Skip to content

Writing Specs

A Flow-Next spec is a durable implementation contract. It should remove ambiguity without turning into pseudocode.

The spec should say what must be true when the work is done, why that matters, and what is out of bounds. The agent should still be free to discover the implementation path inside those boundaries.

  • Problem and target user
  • Business outcome
  • Acceptance criteria with stable R-IDs
  • Constraints and non-goals
  • Edge cases
  • API or data contract expectations
  • Decision context and trade-offs
  • Genuinely-open unknowns, parked under ## Parked unknowns instead of written up as if they were decided
  • File-by-file implementation orders
  • File paths and line numbers as spec content - they rot on the first refactor; state the contract (types, signatures, behaviors) instead. **Files:** / **Touches:** stay a task’s job
  • Premature abstractions
  • Pseudocode
  • “Maybe later” features
  • Renumbering R-IDs after review
  • Restating a standing G-ID from .flow/criteria.md as an R-ID - completion review already judges every G-ID against the spec; reference it in prose and write an R only for what this spec adds (Standing Criteria)
flowchart LR
  Draft["Draft intent"] --> Business["Business interview"]
  Business --> Technical["Technical interview"]
  Technical --> Ready["Spec ready?"]
  Ready -->|gaps| Technical
  Ready -->|ready| Plan["/flow-next:plan"]
  Plan --> Tasks["Spec + tasks"]
  Tasks --> PlanReview["/flow-next:plan-review (optional)"]
  PlanReview -->|gaps| Technical
  PlanReview -->|ship| Work["/flow-next:work"]

Solo developers can collapse the business and technical passes into one conversation. Teams should keep them distinct so product ownership and engineering ownership are visible in the artifact.

/flow-next:plan produces both the spec (with R-IDs) and the task breakdown sized for one work-loop iteration. /flow-next:plan-review is an optional gate between planning and implementation — see Plan Review for when it earns its cost.

The “Spec ready?” diamond above is a real, recorded state since Flow-Next 1.12.0 — not just a judgment you hold in your head. A spec carries a ready boolean (default false) marking it complete enough to hand to an agent:

Terminal window
flowctl spec ready fn-1 # bless the spec (idempotent)
flowctl spec unready fn-1 # back to draft
flowctl specs # ready specs carry a [ready] badge

Readiness is human-owned, never agent-inferred — the spec is where your judgment concentrates, so blessing it is your call. It is orthogonal to status: a ready spec stays open through planning and work. For tracker-connected repos, readiness projects one-way from the tracker instead (tracker.readyState — see Tracker Sync), so the board is the single place the team blesses work.

The gate is opt-in and invisible until adopted. Once it is in use (any spec marked ready, or tracker.readyState configured), /flow-next:capture and /flow-next:interview offer to mark a finished spec ready, and /flow-next:plan soft-warns before planning a spec that is still draft — warn, never block. The flag is the entry gate for autonomous loop execution: /flow-next:pilot selects ready specs only, so a half-baked draft is never executed unattended — and on tracker-connected repos that makes the board the loop’s control plane (see Readiness as the control plane). It stands alone as backlog hygiene too — flowctl specs shows at a glance which specs are blessed vs still-draft.

The bundled canonical scaffold lives at ${PLUGIN_ROOT}/templates/spec.md. In Flow-Next 1.1.3+, /flow-next:capture, /flow-next:interview, and /flow-next:plan resolve the scaffold through a three-tier cascade. First match wins:

  1. <repo_root>/SPEC.md
  2. <repo_root>/spec.md
  3. bundled ${PLUGIN_ROOT}/templates/spec.md

Migrating from a pre-4.0.0 install: a customized .flow/templates/spec.md is no longer read - copy it to a repo-root SPEC.md (tier 1) to keep your scaffold, then delete the old file.

Use the repo-root SPEC.md or spec.md when a project needs a customized scaffold. That matches Flow-Next’s other repo-root guidance files such as STRATEGY.md, CLAUDE.md, AGENTS.md, and GLOSSARY.md.

The bundled plugin template is the fallback and source of truth when the repo has not opted into customization. A leftover .flow/templates/spec.md from an older install is no longer consulted - delete it.

Case-insensitive filesystems such as macOS APFS and Windows NTFS collapse SPEC.md and spec.md to one inode, so Flow-Next treats that as a single tier-one hit. On case-sensitive filesystems where both files exist distinctly, SPEC.md wins and Flow-Next warns.

The bundled section list is a default, not a requirement. Tier one exists so a project can impose its own spec shape without forking the plugin. If your team wants user stories on every spec, a risk register, a rollout runbook, a compliance block or a data-retention statement, add it and every spec that project authors carries it from then on.

Terminal window
# from your repo root
cp "$CLAUDE_PLUGIN_ROOT/templates/spec.md" SPEC.md
$EDITOR SPEC.md
git add SPEC.md && git commit -m "docs: project spec scaffold"

Commit it. The scaffold is a team artifact, and an uncommitted SPEC.md gives you a spec shape your teammates and your CI agents do not have.

Free to change: adding sections, reordering them, and rewriting the guidance prose under any heading. That prose is instruction to the authoring agent, so making it say what your project actually needs is the highest-leverage edit and the one most often skipped.

A worked example - house style for acceptance criteria. Want BDD-flavored criteria on every spec? Rewrite the guidance under ## Acceptance Criteria in your copied SPEC.md:

## Acceptance Criteria
<!-- House style: write each criterion as a BDD scenario - Given <context>,
when <action>, then <observable outcome>. Keep the **R<n>:** marker;
use sibling ids (R4a, R4b) for scenario variants of one requirement. -->
- **R1:** Given a signed-in user with an expired session, when they submit the form, then the draft is preserved and they are returned to it after re-auth.

Every interview and capture pass then writes criteria in that shape. Only the heading name and the **R<n>:** bullet marker are load-bearing - the criterion text is yours (the parser reads wrapped multi-line bullets, and anything unreadable surfaces as acceptance_criteria_residue rather than being silently dropped). The same move carries any house convention.

More house styles, same mechanism - each is one instruction comment (or one rewritten guidance line) in your copied SPEC.md, and every spec the project authors from then on obeys it:

Team wantsInstruction to add under the relevant heading
User-story criteria (product orgs)Under ## Acceptance Criteria: “Phrase each criterion as: As a <role>, I can <capability>, so that <outcome> - observable outcome mandatory, keep the **R&lt;n&gt;:** marker.”
Observability mandate (SRE-minded)Under ## Acceptance Criteria: “Every criterion names the signal that proves it in production - a metric, log line, or alert. A criterion with no signal is not accepted.”
Compliance traceability (regulated)Under ## Acceptance Criteria: “Tag each criterion with the control it satisfies (e.g. [SOC2-CC6.1], [GDPR-Art17]).” Plus an added ## Data & Compliance section: classification of data touched, retention, audit-trail impact.
Non-functional budgetsAn added ## Budgets section: “State the numeric budgets this change must hold: p95 latency, bundle-size delta, WCAG level. ‘Fast’ is not a budget; ‘<200ms p95’ is.”
Rollout & revert (ops)An added ## Rollout section: “Feature flag or migration? Name the flag, the rollout stages, and the tested revert path. A migration with no revert statement is incomplete.”
Test-evidence ruleUnder ## Acceptance Criteria or ## Boundaries: “Every criterion names the command or test file that will prove it - the worker baselines and re-runs exactly those.”
Specs in your languageRewrite the guidance prose in German/French/Japanese/… - the authoring passes follow the instruction language, and the four parsed headings plus **R&lt;n&gt;:** markers stay English so the tooling keeps reading them.

Composable with additional role passes: a platform or quality role’s standing section is just another row of this table.

Handle with care: four headings are parsed by flowctl. Renaming or removing one does not raise an error, it silently drops a feature.

HeadingWhat stops working
## Acceptance CriteriaR-ID extraction. Coverage tables come out empty, task satisfies: mapping breaks, unaddressed-R-ID verdict gating stops firing. Legacy ## Acceptance criteria and bare ## Acceptance are still tolerated.
## BoundariesThe “Not in this PR” section of a generated PR body loses its source.
## Goal & ContextThe business interview pass loses a write target.
## Decision ContextFlat-versus-substructured detection for ### Motivation / ### Implementation Tradeoffs.

Keep R-ID bullets in the form - **R1:** <criterion>, with optional single-letter siblings such as R4a. Prose numbering like “Requirement 1” is not recognized.

Enumerate error cases inside the criterion. Each behavioral R-ID states its error / invalid-input / boundary handling as sub-clauses in the same bullet — or explicitly records “no error surface beyond X”. A one-line “none” declaration is a complete enumeration; silence is not, and a reviewer can’t tell considered-and-none from forgot. The work stage inherits enumerated cases as required tests, which is exactly where untested error paths otherwise entrench: a persisted regression suite reports “all green” for the cases nobody enumerated. Error cases stay prose sub-clauses — no sub-R-IDs, no extra coverage-table columns. Standing G-IDs keep their own error handling; don’t restate it per spec.

Mark your sections so the interview can fill them. flowctl scope write-policy enumerates the seven canonical sections only, so a section you add sits in neither its writable nor its preserved list. Ownership of a project-added section therefore comes from the section’s own scope-owner marker, and /flow-next:interview applies a three-way rule:

Marker on your sectionWhat an interview pass does
names the pass’s own scope, e.g. <!-- scope: business --> under --scope=businesswrites it, filling and refining it like a canonical section
names the other scopepreserves it byte-for-byte
<!-- scope: both -->writable under any pass
absent or unparseablepreserves it byte-for-byte and says so in the read-back

The marker is the difference between a section that gets filled and one that stays frozen. Add one when you want the interview to do the work, leave it off when the section is yours to hand-write. Two consequences: a marked section is rewritable, so hand-authored content under a marker you own will be refined by the next pass of that scope, and scope-owner markers must be kept on project-added sections rather than stripped, because for those the marker is the only ownership signal a later pass has.

/flow-next:capture and /flow-next:plan seed from the template directly and are unaffected.

We tested widening the bundled scaffold with user-story and test-seam sections. A first pass looked promising, a pre-registered replication did not hold up, and the wider scaffold ran roughly a third longer, which every worker and reviewer downstream pays to read. So the default stays lean and this override stays the place to express a project preference.

/flow-next:setup Step 4a offers a repo-root spec template when neither <repo_root>/SPEC.md nor <repo_root>/spec.md exists:

Copy template / Skip / abort

On consent, setup writes <repo_root>/SPEC.md from the bundled template. It always writes uppercase SPEC.md, preserves file mode 0644, and never creates lowercase spec.md.

On re-setup, Flow-Next uses a byte-compare gate:

Keep mine / Overwrite with canonical / abort

The compare normalizes CRLF to LF and ignores trailing-newline differences, so real project customizations are not silently clobbered.

**R1:** Users can create a spec from conversation context.
**R2:** Each generated acceptance criterion is source-tagged as [user], [paraphrase], or [inferred].
**R3:** The agent shows a read-back before writing files.

When a spec is captured from a chart briefing package, keep three provenance lanes separate:

  1. Chart decision provenance - structural: D-ID, decision type, answer, evidence/assets, supersession, briefing membership (B-ID / cluster key). Cite these as links and references in Goal, Decision context, or Notes - e.g. “Storage approach settled in fn-140.D2 (probe); see briefing B1.”
  2. Acceptance-criterion author provenance - trailing tags [user] | [paraphrase] | [inferred] | [strategy:<track>] on criteria that capture or interview newly write. These answer who grounded this criterion, not how strong the chart evidence was. A criterion derived from a resolved unattended D-ID is not automatically [user].
  3. Verified-versus-inferred technical fact grammar - not used on chart facts or decisions. Do not invent trailing [verified] / [inferred] labels for D-ID evidence.

D-ID evidence never receives acceptance-criterion source tags. Capture and interview tag only the criteria they author; they never retag an earlier pass.

Business pass:

Terminal window
/flow-next:interview fn-1 --scope=business

Technical pass:

Terminal window
/flow-next:interview fn-1 --scope=technical --strategy --docs

Both passes write to the same spec file. They are states of one evolving artifact, not separate documents.

The business pass should answer:

  • Who is affected?
  • What behavior must exist?
  • What customer or operational outcome defines success?
  • What is explicitly not in scope?
  • What can the agent infer, and what must remain human-owned?

Good product acceptance criteria are observable:

**R1:** A maintainer can run a single command to generate a PR body from an implemented spec.
**R2:** The PR body lists every acceptance criterion and whether it has evidence.
**R3:** Deferred review findings appear in the PR body instead of being hidden in agent context.

The technical pass should add:

  • System boundaries and integration points.
  • Data model or file format expectations.
  • Security, permissions, and migration constraints.
  • Test strategy and evidence expectations.
  • Known risky files or modules.

Do not write a step-by-step implementation unless the sequence is itself a requirement. Let /flow-next:plan map the spec to the codebase after the spec is stable.

Business and technical are the two built-in scopes, not the ceiling. Field teams add passes for the roles their organization actually gates on — platform/cloud enablement (deployment topology, IAM and secrets, cost and quota constraints, observability requirements) and quality (test-environment needs, regression surface, release-gate criteria) are the common two.

Two mechanics, composable:

  • Standing sections via the scaffold. Copy the bundled template to a repo-root SPEC.md and add the sections those roles always fill (## Platform & operations, ## Quality gates, …) — every new spec then arrives with the role’s checklist built in, and the guidance prose inside a section is free to rewrite. See Customizing the scaffold; the four parsed headings are the only ones that must keep their names.
  • A focused interview pass per role. Run /flow-next:interview fn-N and steer it in conversation — “interview from the platform-engineering angle: deployment, IAM, cost, observability” — or simply discuss the concerns in chat and let the interview’s write-back consent fold them in. R-IDs are append-only and source-tagged, so a platform pass adds R14-R16 without disturbing the product criteria, and the coverage table later shows exactly which task satisfies the ops requirement.

The same rule binds every pass: each role states what must be true, not how to build it. An ops criterion like “R14: the service exposes a health endpoint the deploy gate can probe” ages well; “use Terraform module X” belongs in the technical constraints only if it genuinely is a constraint.

Run the checklist:

  • Every requirement has an R-ID.
  • Non-goals are explicit.
  • Ambiguous words like “fast”, “simple”, “secure”, or “nice” are translated into observable criteria.
  • Product assumptions are marked as product assumptions, not engineering facts.
  • Technical constraints are constraints, not preferences.
  • The spec can be reviewed by someone who never saw the original chat.