# Writing specs

Source: https://flow-next.dev/guides/writing-specs/

What belongs in a spec, how the ready flag gates it, how capture and refine divide the work, and the patterns that keep a team spec useful.

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.

## Include

* 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

## Avoid

* 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](https://flow-next.dev/reference/standing-criteria/))

## Spec authoring loop

```mermaid
flowchart LR
  Draft["Draft intent"] -->|"named decision open"| Refine["/flow-next:refine"]
  Draft -->|"nothing open"| Ready
  Refine --> Ready["Spec ready?"]
  Ready -->|"named decision open"| Refine
  Ready -->|"ready, no plan signal,<br/>no design risk named,<br/>no review requested"| Work["/flow-next:work --no-plan"]
  Ready -->|"ready, no plan signal,<br/>design risk named or<br/>review requested"| PlanReview
  Ready -->|"ready, positive plan signal"| Plan["/flow-next:plan"]
  Plan --> Tasks["Spec + tasks"]
  Tasks --> PlanReview["/flow-next:plan-review"]
  PlanReview -->|gaps| Refine
  PlanReview -->|"ship, no tasks"| Work
  PlanReview -->|"ship, tasks"| PlannedWork["/flow-next:work"]
  Work --> Gates["configured review, QA per pipeline.qa"]
  PlannedWork --> Gates
  Gates --> PR["/flow-next:make-pr"] --> H(["PR - your review"])
```

Refine is one question pass over whatever decisions are still open. Teams can run it once per role, each with its own lens, so product ownership and engineering ownership are visible in the artifact.

Capture and refine produce the spec with its R-IDs. `/flow-next:plan` produces the task breakdown, sized for one work-loop iteration, and runs only on a positive signal; the default for a ready spec is `/flow-next:work <spec-id> --no-plan`. `/flow-next:plan-review` reviews the design on a spec with zero tasks - see [Plan Review](https://flow-next.dev/skills/plan-review/) for when it earns its cost.

For a visual view of a spec or plan, run [`/flow-next:visual`](https://flow-next.dev/skills/visual/), or ask the agent for an HTML page when you want one. Markdown stays the record.

## The ready flag

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**:

```bash
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 **yours, never agent-inferred** - the spec is where your judgment concentrates, so blessing it is your call. The one exception is [goal mode](https://flow-next.dev/autonomy/goal-mode/): a goal you name is your consent for the child specs it mints, and it readies those children only. 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](https://flow-next.dev/integrations/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`](https://flow-next.dev/skills/capture/) and [`/flow-next:refine`](https://flow-next.dev/skills/refine/) offer to mark a finished spec ready, and [`/flow-next:plan`](https://flow-next.dev/skills/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:flow --auto`](https://flow-next.dev/autonomy/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](https://flow-next.dev/autonomy/pilot/#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.

`flowctl spec set-no-plan fn-1` records explicit consent to implement a ready cohesive spec directly through Flow-Next work. It is refused once tasks exist; `clear-no-plan` undoes it. `flow --auto` routes a ready marked spec to one execution owner covering the full spec, with the same configured gates; for a ready spec with no recorded route it records the route itself before any mint. Readiness and no-plan consent remain separate, human-owned and flow-local choices; the route is never inferred from a model name. For a ready spec with no tasks, direct execution through `/flow-next:work <spec-id> --no-plan` is the default. Plan is chosen only on a positive signal: you asked for a plan, separate human owners will implement, or delivery is staged across several PRs. Risk, size, and file count never trigger plan on their own. Design risk routes to `/flow-next:plan-review`, which reviews a zero-task spec; unresolved product or authority choices route to `/flow-next:refine`.

## Capture first, then sharpen

Two skills write specs, and using them in the wrong order is the most common way to get a weak one.

[`/flow-next:capture`](https://flow-next.dev/skills/capture/) is the intake valve. It does not want a requirements document, it wants whatever carries the intent: a conversation you just had, a briefing packet, a well-described ticket, research the agent just ran, a prototype, or a [chart](https://flow-next.dev/skills/chart/) briefing package. It settles “one spec or several?” at intake with a spec-count proposal, and it leaves your own words untagged and tags every acceptance criterion it wrote `[paraphrase]`, `[inferred]`, or `[strategy:<track>]`.

[`/flow-next:refine`](https://flow-next.dev/skills/refine/) is a sharpening tool rather than intake. It shines *after* the spec exists, pointed at what is actually soft:

* **Burn down the guesses.** Aim it at the tagged lines: *“interview me on this spec regarding anything that has been inferred”*. Questions then land on what the agent guessed rather than what you already stated, and only on the guesses that would change the build.
* **Pressure-test one requirement.** *“Technical interview regarding R9”* keeps the questions on the single criterion that worries you, and still asks only what would change the build.
* **Settle a fork the code cannot.** Refine earns its time when a costly-to-reverse choice is still open: a data model or migration, a public contract, a security boundary. Edge cases and performance detail are left to implementation, review, and QA.

Refine asks a question only when a wrong guess would build the wrong thing, nothing but you can settle it, and it is your call. A session that finds nothing like that reports that the spec is clear enough to build, and that is a good result. Flow and capture recommend refine only when they can name such a decision ([when to refine](https://flow-next.dev/skills/refine/#when-to-refine)).

Capture marks the guesses, and you review them at its read-back. Point refine at a guess only when getting it wrong would build the wrong thing. “Interview me on this ticket” skips the step that makes the questions precise.

* **Read first when the spec names something new.** `/flow-next:refine fn-N --scope=research` asks nothing: four read-only scouts (docs, practice, docs-gap, memory) resolve library versions, changed APIs, gotchas, and the repo docs that must change into one `## Resolved via Research` section, a source on every bullet. It skips itself, with the reason printed, when the section already exists or plan’s tasks already carry the scout findings; `--force` reruns it.

**Push back on the options.** Teams treat each question’s proposed options as a multiple-choice test and pick the recommended one, which inverts the tool. The recommendation is a starting position with a stated confidence. When none of the options fit, that is the interview working: answer in your own words, send it to investigate the codebase first, redirect the line of questioning, or park the decision. An interview where you picked the recommended option every time laundered the AI’s guesses into requirements with your name on them.

**Skips are not answers.** Only an explicit answer, or an explicit “you decide” delegation, resolves a question. Everything skipped parks under `## Open Questions` with an owner hint and the agent’s unconfirmed leaning, and a consent checkpoint runs before write-back.

## Scope split for teams

Each person runs refine in their own session with their own lens:

```bash
/flow-next:refine fn-1 --biz
/flow-next:refine fn-1 --tech --strategy --docs
```

`--biz` and `--tech` are aliases for `--scope=business` and `--scope=technical`; any other value (`--scope=qa`, `--scope=security`) works the same way. Every session writes to the same spec file, and the read-back before writing names every section it changed, so each person sees any change outside their layer. The sessions are states of one evolving artifact, not separate documents. Run a session only when it has an open decision to settle; empty technical sections are left for the implementation to fill.

## Product-owner session

A product session (`--biz`) settles whichever of these the spec leaves open and only the product owner can decide:

* 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:

```md
**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.
```

## Technical session

A technical session (`--tech`) settles the costly-to-reverse technical choices the code does not answer:

* System boundaries and external interfaces.
* Data model, migration, or file format commitments.
* Public contracts and security or permission boundaries.

Test strategy, performance detail, and edge cases are left to implementation, review, and QA unless the spec is about them.

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.

## Additional role passes

The scope is free text, so business and technical are 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 spec scaffold](https://flow-next.dev/guides/spec-scaffold/#adding-your-own-sections); the four parsed headings are the only ones that must keep their names.
* **A focused interview pass per role.** Run `/flow-next:refine fn-N --scope=platform` (or `--scope=qa`), or steer it in conversation - *“interview from the platform-engineering angle: deployment, IAM, cost, observability”* - or simply discuss the concerns in chat and let the refine pass’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.

## Good acceptance criteria

**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.

```md
**R1:** Users can create a spec from conversation context.
**R2:** Each generated acceptance criterion the agent wrote is source-tagged as [paraphrase] or [inferred]; the user's own words stay untagged.
**R3:** Capture saves the spec, offers the saved file for editing, and validates any edits before continuing.
```

## Briefing evidence from chart

When a spec is captured from a [chart](https://flow-next.dev/skills/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 `[paraphrase] | [inferred] | [strategy:<track>]` on criteria that capture or refine **newly write**; the user’s own words stay untagged. 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 the user’s words.
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 refine tag only the criteria they author; they never retag an earlier pass.

## Spec-as-PR

For high-impact changes, open a PR containing only `.flow/specs/<id>.md` and review the spec before code lands. That moves the high-leverage discussion upstream.

```mermaid
flowchart LR
  Branch["feature branch"] --> Spec["Spec-only PR"]
  Spec --> Product["Product review"]
  Spec --> Tech["Technical review"]
  Product --> Frozen["Frozen R-IDs"]
  Tech --> Frozen
  Frozen --> Implementation["Implementation PR"]
```

Reach for it when a spec crosses team boundaries, user-facing behavior, permissions, data migrations, billing, enterprise controls, or production risk. Reviewing a spec-only PR is much cheaper than reviewing a finished implementation that encoded the wrong product decision. Once that PR is approved, implementation has a frozen contract.

## Constraint ledger

For team specs, keep constraints visible instead of burying them in prose:

| Constraint         | Example                                    |
| ------------------ | ------------------------------------------ |
| Product boundary   | ”Do not add billing plan management.”      |
| Technical boundary | ”No database migration in this spec.”      |
| Compatibility      | ”Existing config files continue to load.”  |
| Operational        | ”Failure must leave `.flow/` state valid.” |
| Security           | ”No secrets are written to receipts.”      |

The ledger keeps the agent from optimizing away a constraint a human assumed was obvious.

## Decision records

Record a load-bearing decision when it is hard to reverse, surprising without context, and represents a real trade-off. R-IDs are frozen after review. New criteria append, removed criteria leave gaps.

## The pre-planning checklist

Run the checklist:

* Every requirement has an R-ID.
* Non-goals are explicit.
* A number you stated is an acceptance criterion; a preference such as “fast” or “simple” stays in Decision Context as guidance.
* 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.

Project-specific spec shapes (extra sections, house style for criteria, the headings `flowctl` parses) are in [Customizing the spec scaffold](https://flow-next.dev/guides/spec-scaffold/).
