Spec Req
Look up requirements by ID or category, trace them through the code, and surface gaps in both directions. Also handles creating new requirements.
%%{ init: { 'look': 'handDrawn' } }%%
flowchart TD
Start(["/sextant:spec-req arg"]) --> Parse
Parse{"Parse argument"} -->|"XX-NN"| Single["Single requirement lookup"]
Parse -->|"XX"| Category["Category lookup"]
Parse -->|"new"| New["New requirement"]
Parse -->|"init"| Init["Bootstrap new spec"]
subgraph "Bootstrap"
Init --> HasDoc{"from doc?"}
HasDoc -->|"no"| Vision["Gather vision / scope"]
HasDoc -->|"yes"| ReadDoc["Read source document"]
ReadDoc --> Vision
Vision --> Sections["Confirm category set"]
Sections --> Location["Choose spec location"]
Location --> Scaffold["Write SPEC.md skeleton + STATUS.md stub"]
Scaffold --> FromDoc{"from doc?"}
FromDoc -->|"yes"| Populate["Extract EARS requirements into SPEC.md"]
FromDoc -->|"no"| Handoff["Hand off to 'new' for first requirements"]
Populate --> Handoff
end
subgraph "Lookup"
Single --> FindSpec["Locate SPEC.md"]
Category --> FindSpec
FindSpec --> Extract["Extract matching requirements"]
Extract --> Table["Present requirement table"]
end
subgraph "Trace"
Table --> Search["Search the code"]
Search --> Gaps["Report gaps both directions"]
end
subgraph "Create"
New --> FindSpec2["Locate SPEC.md"]
FindSpec2 --> Classify["Auto-assign category + number"]
Classify --> Confirm["User confirms"]
Confirm --> Write["Write to SPEC.md"]
end
Locate the spec
Find the current SPEC.md using the shared discovery order in
references/locate-spec.md (the source of
truth every sextant skill uses). In brief, first hit wins: STATUS.md
spec-pointer → spec/ directory (incl. vnext/, exploration/, migration/)
→ justfile spec variable → root SPEC.md (or docs/spec.md).
If no SPEC.md is found, ask the user where it is.
Spec layout
Requirements are laid out per
references/spec-layout.md (the source of
truth every sextant skill uses). In brief: a category is a heading whose text is
its backticked prefix with the full name beneath it, and each requirement is a
heading one level below whose text is its backticked ID — so SPEC.md#locate-01
links to one requirement. A spec predating that layout writes each requirement
as a - **[XX-NN]** … bullet; read that form too, and match whichever form the
file already uses when writing into it. Offer to convert such a spec to headings
as its own whole-file edit — never as a side effect of adding a requirement.
When a requirement no longer holds, retire it: strike the heading and its
statement in place per references/spec-layout.md's Retired requirements
section, never delete it, and never reuse the ID.
Mode: Single requirement (sextant:spec-req XX-NN)
Look up one requirement by its full ID.
Find the requirement in SPEC.md. If the ID doesn't exist, say so and suggest nearby IDs in the same category.
Present a table with the requirement and its status against the code:
| ID | Requirement | Status | Location |
|-----------|---------------------|---------|---------------------|
| CONFIG-01 | Config file loading | Covered | src/cfg.py (`load`) |
Trace it through the code to find gaps:
- Implementation gaps — the spec says something the code doesn't do. Search for the requirement ID in comments, grep for keywords from the requirement text, and read relevant code to verify behavior.
- Spec gaps — the code around this requirement does something the spec doesn't mention. Look at neighboring code, recent commits touching related files, and STATUS.md notes.
Report findings below the table:
Gaps found:
→ missing env var override (CONFIG-01 says "env vars take precedence")
→ Spec gap: YAML config is supported (src/cfg.py, `load_yaml`) but the spec only mentions JSON
If no gaps are found, say so — a clean result is useful information.
Mode: Category lookup (sextant:spec-req XX)
Look up all requirements in a category.
Extract all requirements matching the category prefix from SPEC.md.
Present a summary table with implementation status:
## CONFIG — Configuration
| ID | Requirement | Status | Location |
|-----------|-----------------------|---------|--------------------------|
| CONFIG-01 | Config file loading | Covered | src/cfg.py (`load`) |
| CONFIG-02 | Env var overrides | Partial | src/cfg.py (`apply_env`) |
| CONFIG-03 | Validation on startup | Missing | — |
Trace each requirement through the code, same as single-requirement mode but summarized. Only report gaps — don't repeat "no gaps" for every covered requirement.
Category summary at the bottom:
Coverage: 1/3 active requirements (33%)
Gaps: 2 implementation gaps, 1 spec gap
Mode: New requirement (sextant:spec-req new)
Guide the user through creating a new requirement. This replaces the standalone spec-new-req skill.
Step 1: Classify
Read existing categories and their highest requirement numbers from the spec.
Based on the user's description:
- Match to an existing category if it fits. Use the next available number.
- Create a new category if none fits. A prefix is more than one character, all caps, and a single word with no punctuation — the name people already say for the thing, usually the whole word (
RENDER, notRN) but the initialism where that's what they say (UX,CI,CLI). The authoritative form and the naming trade-offs are inreferences/category-prefix.md. - Draft the requirement text in EARS syntax — choose the pattern that fits the requirement's activation. The five patterns (Ubiquitous, State-Driven, Event-Driven, Optional, Unwanted Behaviour) and how they combine live in
references/ears-patterns.md.
Present for confirmation:
Proposed requirement:
XX-NN <drafted requirement text>
Category: <name> (existing|new)
Does this look right?
Step 2: Scope check
After the user confirms:
- Trivial/isolated — ask: "This looks straightforward — implement now?"
- Non-trivial — ask: "This touches [scope]. Implement now, or file it as an issue?"
A requirement the project is not committing to does not go in SPEC.md. The spec is what the code is measured against, so an entry nobody is building against reads as a coverage gap forever. Point the user at their issue tracker instead and leave the spec alone.
Step 3: Write
If implementing now:
Add to SPEC.md in the appropriate category section, sorted by ID, as its own heading — the backticked ID, one level below the category heading, with the EARS statement beneath it:
#### `CONFIG-04` When a config key is missing, the system shall exit non-zero naming the key.A new category gets its own heading first — the backticked prefix, its full name on the next line. (Match the file instead if this spec still uses the inline
- **[XX-NN]**form; see Spec layout above.)Record it in STATUS.md as "Missing" (the shared Covered/Partial/Missing/Contradicts vocabulary)
Flow to the code — make the change, update STATUS.md to "Covered"
If not implementing now: write nothing. Say which category it would have landed in and what the EARS statement would be, so the user can paste it into an issue, and stop there.
Confirm:
Captured XX-NN: <short description>
→ SPEC.md updated (SPEC.md#xx-nn)
→ STATUS.md updated (if applicable)
Mode: Bootstrap a new spec (sextant:spec-req init [from <doc>])
Stand up a spec-driven repo from scratch — there's no SPEC.md yet. This is the
zeroth authoring step: once the skeleton exists, every other mode (and the rest
of sextant — spec-sync, spec-status) operates on it.
Two variants:
init(no source) — gather vision conversationally and scaffold an empty skeleton; requirements come next vianew.init from <doc>— treat<doc>(a path or URL to a PRD, design doc, README, RFC, …) as a requirements source: derive vision and concepts from it, then extract its requirements into SPEC.md as EARS statements. The result is a populated spec, not an empty skeleton.
The steps below are shared; the from <doc> additions are called out inline.
Step 1: Confirm there's no spec already
Run the locate order from the top of this skill. If a spec already exists,
stop and say so — init is for fresh repos; use new to add requirements
to an existing spec.
Step 2: Gather vision and scope
With from <doc>: read the source first (Read for a path, WebFetch for a
URL), then derive the vision, key concepts, and candidate requirement
categories from its content instead of asking. Confirm the derived contract in
one pass rather than interviewing field by field — they can correct the draft.
The category set still goes through Step 2b; a set derived from a document is a
draft, not the user's decision. Keep the document open; Step 4b extracts
requirements from it.
Without a source — ask for:
- What the project does — the one- or two-sentence contract. This becomes the spec's opening line.
- Key concepts/nouns the spec will reference — these seed the Concepts section so requirement text has defined terms to lean on.
- Anticipated requirement categories (config, CLI, rendering, …) — used to
seed empty category sections. Don't force this; a single starter category is
fine, and more get added via
new. - Whether the spec will be versioned — informs the spec location in Step 3.
Step 2b: Walk the user through the category set
The categories are the one decision here that's expensive to revisit: the prefix is baked into every ID, and renaming one later re-IDs its requirements and stales every ledger row, audit entry, and code anchor that cites them. So name them with the user before anything is written, rather than scaffolding a set they'll correct later.
Draft the set from Step 2 (or, with from <doc>, from the document's own
structure) and walk it section by section — one row each, with the prefix, the
name, and one line on what belongs in it and what doesn't:
Proposed sections:
CONFIG — Configuration loading, precedence, validation of settings
CLI — Command surface flags, subcommands, exit codes
RENDER — Output rendering formats and templates the CLI emits
Prefixes are all-caps single words, more than one character
(references/category-prefix.md). Rename, split, merge, or drop any — and say
what's missing. Nothing is written until you're happy with this set.
Take the user through it explicitly: ask whether each section is one they'd
recognize six months from now, and whether any behavior they care about has no
section to land in. Iterate until they sign off. Apply the naming rule in
references/category-prefix.md to
anything they propose, and say so when a suggested prefix needs adjusting to
meet it — don't silently rewrite their word.
Step 3: Choose the spec location
Per the standard locate order:
- Root
SPEC.md— simplest, and the default; for a new project or an existing one adopting spec-driven in place. spec/<version>/SPEC.md(e.g.spec/v1/SPEC.md) — the versioned layout, for a spec expected to go through revisions worth keeping side by side. Record the version in the justfilespecvariable so the other skills resolve it.
Ask which; default to root SPEC.md.
Step 4: Scaffold
Write the SPEC.md skeleton — an EARS preamble, a Concepts section, and empty categorized requirement sections (no requirements yet):
# <project> — Specification
<one- or two-sentence contract from Step 2>
Requirements use [EARS syntax](https://alistairmavin.com/ears) — each is one of:
Ubiquitous (`The <system> shall …`), State-Driven (`While …`), Event-Driven
(`When …`), Optional (`Where …`), or Unwanted Behaviour (`If … then …`).
Each requirement is its own heading carrying a stable ID (`XX-NN`), one level
below its category's heading, so every requirement has a linkable anchor
(`SPEC.md#xx-nn`). Lettered decompositions (`XX-NNa`) each count as one.
## Concepts
- **<term>** — <definition>
## Requirements
### `<PREFIX>`
<category name>
_(none yet — add with `/sextant:spec-req new`)_
Then write a STATUS.md stub in the canonical shape that spec-status
maintains (zero requirements so far), recording the spec location it tracks.
Without a source: do not invent requirements during init. The skeleton is
empty by design; requirements come next via new. (This restriction is lifted
for from <doc>, where the document is the requirements source — see below.)
Step 4b: Populate from the source document (from <doc> only)
When a source document was passed, the category sections are not left empty — extract the document's requirements into them:
- Extract candidate requirements from the document read in Step 2. Capture every distinct behavior, constraint, or rule it states — favor coverage; the user prunes in confirmation.
- Classify and number each candidate into the categories confirmed in
Step 2b (the same classification logic as
newStep 1: match an existing category or mint a prefix perreferences/category-prefix.md, assign the next number per category). A candidate that fits no confirmed category is a signal the set is incomplete — take the proposed new section back to the user rather than minting it silently. - Draft each in EARS syntax — pick the
pattern that fits the requirement's activation (Ubiquitous, State-Driven,
Event-Driven, Optional, Unwanted Behaviour), exactly as in
newStep 1. - Present the full extracted set for confirmation as a table grouped by category, so the user can correct wording, drop noise, or re-bucket before anything lands:
Extracted N requirements from <doc>:
CONFIG — Configuration
CONFIG-01 When the CLI starts, the system shall load config from <path>.
CONFIG-02 If a required key is missing, then the system shall exit non-zero.
RENDER — Rendering
RENDER-01 The system shall render output as <format>.
Confirm, edit, or drop any before I write them.
- Bulk-write the confirmed set into SPEC.md under their category sections,
one ID heading each (per Spec layout above), sorted by ID, replacing the
_(none yet …)_placeholders. Seed STATUS.md accordingly (every requirement starts uncovered, since no implementation exists yet). Note in your summary that these requirements are derived from the document and should be reviewed against it — extraction is a draft, not an authority.
Step 5: Hand off
Report what was scaffolded.
Without a source — flow into new for the first real requirement:
Scaffolded:
→ SPEC.md (EARS preamble, Concepts, N empty category sections)
→ STATUS.md stub
→ spec location: <root SPEC.md | spec/v1/SPEC.md, justfile spec=v1>
Next: add your first requirement — /sextant:spec-req new
With from <doc> — report the populated spec:
Scaffolded from <doc>:
→ SPEC.md (EARS preamble, Concepts, N requirements across M categories)
→ STATUS.md (N requirements, all uncovered)
→ spec location: <root SPEC.md | spec/v1/SPEC.md, justfile spec=v1>
Review the extracted requirements against the source, then refine with
/sextant:spec-req new or start building against them.