Standing Criteria
Some acceptance criteria outlive every spec that ever cites them. “Every route change regenerates the API contract.” “No new dependency without a health check.” “User-facing strings live in the i18n catalog.” They belong to the project, not to one feature.
Before Flow-Next 3.10.0 they lived in CLAUDE.md prose and reviewer memory, which means they got applied when someone remembered them. .flow/criteria.md gives them a file. When it exists, the spec completion review you already run judges every criterion against the whole implementation and writes the verdicts into the ordinary review receipt.
There is no audit pass, no rule engine, and no score. The reviewer that already reads your diff gets your standing rules alongside the spec.
flowchart LR Criteria[".flow/criteria.md<br/>G1, G2, G3"] --> Review["spec-completion-review"] Spec["spec R-IDs"] --> Review Diff["combined diff"] --> Review Review --> Receipt["receipt<br/>criteria: met / violated / n/a"] Review --> Findings["findings<br/>(every violation, with detail)"]
The file
Section titled “The file”One line-anchored bullet per criterion. It is the R-ID grammar with a G prefix, lifted from one spec to the whole project:
- **G1:** Every route change regenerates the API contract.- **G2:** No new dependency without a health check (scope: package.json).- **G3:** User-facing strings live in the i18n catalog (scope: src/ui/**).Rules that matter when you write it:
- The bullet starts at column 0. Indented and nested bullets are ignored, as are lines commented out inline (
<!-- - **G1:** ... -->). The parser does not track multi-line comment blocks, so a column-0 criterion inside a block comment still counts as active. - Ids are unique; gaps are fine. Deleting G2 leaves G1 and G3.
- Never renumber. G-IDs are stable identity across specs, receipts, and PR history, the same rule R-IDs follow inside a spec.
- Scope hints live in the prose itself (
(scope: src/api/**)). The parser stores prose; the reviewer reads it. - At most 100 active criteria, the same cap the receipt-side parser enforces. A larger file fails validation instead of silently degrading the receipt.
The file is yours. It sits in the repo, so a criteria change goes through the same PR review as code, and nothing in Flow-Next rewrites it after it exists.
G-IDs and R-IDs stay separate
Section titled “G-IDs and R-IDs stay separate”A G-ID is a project invariant; an R-ID is one spec’s own deliverable. The spec-authoring skills (plan, capture, interview) never restate a standing criterion as an R-ID: a copy would freeze while criteria.md evolves, and the reviewer would judge the same rule twice with two chances to disagree. A spec that touches a standing rule references the G-ID in prose and writes an R-ID only for what it requires beyond it.
What the review judges
Section titled “What the review judges”The spec is the unit of compliance. Spec completion review is the only surface that judges criteria, on every review backend (rp, codex, copilot, cursor, host). Per-task implementation review does not, which keeps a project-wide rule from being asked of a task that only owns part of the change.
Each criterion gets exactly one status:
| Status | Meaning |
|---|---|
met | The implementation complies with the criterion. |
violated | The implementation breaks it. Also reported as a normal finding, at reviewer-judged severity. |
n/a | The criterion does not apply to this change. |
Violations land in the findings list you already read, so a criteria file adds nothing new to watch. The compliance array is the record of the judgment; the findings carry the detail.
What lands in the receipt
Section titled “What lands in the receipt”Completion-review receipts (type: completion_review only) gain an additive criteria array beside findings:
"criteria": [ {"id": "G1", "status": "met"}, {"id": "G3", "status": "violated", "note": "route added without contract regen"}, {"id": "G4", "status": "n/a"}]idis a G-ID (G<digits>, unique within the array),statusis exactly one ofmet/violated/n/a, andnoteis an optional one-liner up to 400 characters.- The array is authoritative for compliance status. Findings carry the explanation. No cross-validation links the two, so a consumer renders compliance from
criteriaand detail fromfindingsindependently. - The recorded ids must match your configured criteria exactly before anything attaches.
- Ambiguous, duplicate-id, or oversized reviewer output degrades the array to absent, never to a wrong verdict. Receipts written before 3.10.0 stay valid.
- No
.flow/criteria.mdmeans no criteria content in the prompt and nocriteriafield in the receipt.
Full receipt contract: Receipts.
Validation fails closed
Section titled “Validation fails closed”A criteria file that exists but is broken stops the review before a round is spent:
- typo’d bullets in any Markdown style (
- **G1**: prose,- G1: prose,* **G1:** prose), - duplicate ids or empty prose,
- an unreadable file or a dangling symlink,
- more than 100 active criteria.
Each of these surfaces a validation error instead of quietly running the review without your rules. Fix the file, then re-run:
flowctl criteria listAn absent file is the opposite case and is a silent no-op everywhere. A repo with no criteria file pays not one token of prompt content, and sees no prompts, badges, or warnings.
Adding the file
Section titled “Adding the file”/flow-next:setup offers to scaffold it, once, while the file is absent:
Scaffold
.flow/criteria.md? A plain markdown file of standing, project-wide acceptance criteria. When present, spec completion review judges every criterion against each spec’s implementation and records met/violated/n-a in the review receipt. Absent = zero effect anywhere.
Declining leaves no trace. An existing file is never re-asked about and never touched, whether it was scaffolded, hand-written, or heavily customized. The scaffold documents the grammar and ships its examples commented out, so a freshly scaffolded file parses to zero active criteria and changes nothing until you write your own.
You can also skip setup entirely and create the file by hand. The grammar above is the whole contract.
Two plumbing commands. Both are parse-and-validate only, since judging compliance belongs to the review:
# Parse + validate. Absent or empty file -> empty list, exit 0.# Invalid file -> nonzero exit listing every problem.flowctl criteria list [--json]
# Print the completion-review injection block. Empty output + exit 0 when the# file is absent or has no active criteria; nonzero + errors on stderr (and# empty stdout) when an existing file is invalid.flowctl criteria prompt-block{"success": true, "criteria": [{"id": "G1", "text": "Every route change regenerates the API contract."}], "count": 1, "path": "/abs/path/to/repo/.flow/criteria.md"}path is absolute, and null when the file is absent.
The RepoPrompt and host completion-review workflows call criteria prompt-block and check its exit status before reserving a review round, which is why a broken file costs you nothing but the fix. Subprocess backends compose the same block internally.
Adopting it
Section titled “Adopting it”Start with the rules your reviewers already repeat in PR comments. Three or four criteria that describe a real project-wide invariant beat twenty aspirations.
- A criterion should be judgeable against a diff. “Every route change regenerates the API contract” is checkable; “code should be clean” is not.
- Put the scope in the prose when a rule only applies to part of the tree.
- Delete a criterion when the project stops caring, and leave its number retired.
- Criteria describe standing policy, not this quarter’s migration. Time-boxed work is a spec.
Related
Section titled “Related”- Spec Schema - R-IDs, the per-spec form of the same grammar.
- Spec Completion Review - the skill that judges them.
- Receipts - the portable compliance contract.
- Collaboration - how teams own the file.