How review scope, anchor and gate make a code review checkable, the built-in rules, and how a person plays an acceptance recipe that is read, triaged and gated. Why a release tag needs one.
Two kinds of proof sit on top of automated tests. A checkable code review shows what was reviewed, where each finding is and why the review stopped. A human acceptance run (called a recette in French, the word the tool uses for the page) shows that a person saw the chain work on screen. Both are evidence you can read afterwards, and both refuse to say "all good" when the evidence is incomplete. This page is for reviewers, team leads and anyone who decides whether a version ships.
A review written in prose cannot be told apart from a review that read twelve files out of forty and found nothing. BMAD+ keeps the review itself with the host agent (it calls no model) and adds the three things only code can guarantee.
What must be reviewed is computed, sealed with a hash and listed, with a named reason for every file left out.
Where a finding is comes from a verbatim quote of the code, located by the CLI. A model’s line number is never trusted.
The verdict is derived from coverage (every selected file accounted for), never from the number of findings.
The identifier you pass (demo-1 below) names a folder, _bmad-output/review/<id>/, where all the evidence lives. The terminal shows a real run on a two-commit Git project: the second commit rewrote a function with an off-by-one loop and also committed a .env file.
review scope, rules, then a gate that says incomplete
$ npx bmad-plus review scope demo-1 --base HEAD~1 --effort high/work/demo/app4/_bmad-output/review/demo-1/scope.json1 file(s) to review, 4 changed lines, 1 unit(s) — scope d29789f5ea88 excluded 1 × secretchecklist: /work/demo/app4/_bmad-output/review/demo-1/checklist.mdplan: high effort, 3 pass(es), refutation passEvery selected file must end in coverage.json as completed, failed or waived (with a reason).$ npx bmad-plus review rules src/cart.jsrule set 77b96cb6389d (built-in)src/cart.js gets 2 rule(s): general code builtin ** javascript-typescript code builtin **/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx} **/*.{vue,svelte}$ npx bmad-plus review gate demo-1demo-1: incomplete — coverage 0/1 completed (0 waived), 0 open finding(s), 0 refuted - no coverage.json: which files were reviewed is not established - no findings.json: write it even when there is nothing to report - 1 selected file(s) never accounted for: src/cart.js# Exit code 2. The `.env` file was excluded as a secret before any pattern could bring it back.
Output captured from a real run. Source: bmad-plus 0.23.0, 2026-10-18
Now the host agent reviews. It reads scope.json and checklist.md (the review rules that apply to the selected files, each once, with the files it covers) and writes two files: findings.json and coverage.json. A finding quotes the code; the file is bound to the scope by its hash.
findings.json (one confirmed finding; the scope hash is shortened)
{
"schema": "bmad-plus/review-findings/1",
"scopeSha256": "d29789f5ea88...",
"findings": [
{
"id": "F1",
"path": "src/cart.js",
"existing_code": "for (let i = 0; i <= items.length; i++) sum += items[i].price;",
"content": "The loop runs one step past the end: items[items.length] is undefined, so reading .price throws.",
"category": "correctness",
"severity": "high",
"confidence": "high",
"disposition": "confirmed",
"evidence": "With items = [{price: 1}], i reaches 1 and items[1] is undefined, so items[1].price throws.",
"trigger": "Any non-empty cart.",
"consequence": "total() throws a TypeError."
}
]
}
$ npx bmad-plus review anchor demo-1/work/demo/app4/_bmad-output/review/demo-1/findings.anchored.json — 1 located, 0 ambiguous, 0 unlocated$ npx bmad-plus review gate demo-1 --emit-check _bmad-output/review/demo-1/check.jsondemo-1: findings — coverage 1/1 completed (0 waived), 1 open finding(s), 0 refuted check: /work/demo/app4/_bmad-output/review/demo-1/check.json# Exit code 1. In the first attempt the finding had no evidence field: the gate said "incomplete" with the reason "a confirmed finding needs evidence" until it was added.
Resolves the refs, diffs from the merge base, selects files, writes scope.json and checklist.md. Options: --base REF (default HEAD~1, or HEAD with --workspace), --head REF (default HEAD), --workspace (working tree including untracked files), --include GLOB, --exclude GLOB (repeatable), --effort low|medium|high.
0; 3 on error
review anchor ID
Validates findings.json, locates every existing_code quote in the head version and writes findings.anchored.json. Credentials in quoted text become [REDACTED].
0 all located; 1 invalid or not located
review gate ID
Validates findings and coverage against the scope hash and derives the verdict. --emit-check FILE.json also writes a CI check document.
0 clean; 1 findings; 2 incomplete; 3 error
review continue ID
Resumes an interrupted review on the same sealed scope and writes continue.json with what is still owed.
0; 3 if the code, the rules or a selected file moved
review compare ID --since EARLIER
Sorts the findings of two reviews of the same work into new, persisting, resolved, refuted and not reviewed.
0; 3 on error
review rules [PATH]
Lists the effective rules, or those that apply to one path.
Every file left out carries a reason: secret (.env*, keys, certificates, credentials/, secrets/, .npmrc, .netrc; checked first, no include pattern brings them back), binary, deleted, excluded-by-pattern, generated-or-vendored (lockfiles, node_modules, dist, build, minified files; an include pattern can bring these back) and not-included. Selected files are cut into ordered units by directory, at most 8 files and 400 changed lines each, so parallel reviewers can share the work deterministically. The review folder itself is never part of the change.
--effort fixes the depth before reading: low is one pass; medium (the default) is two passes and a refutation pass; high is three passes and a refutation pass. A change of 150 lines or more is surveyed before it is read, and a change of 400 lines or more with several units is reviewed in parallel units. The effort is sealed in the scope identity.
Coverage or findings are missing or invalid; they answer another scope; a selected file is unaccounted for or failed; the run record says the review stopped before the end or abandoned a unit; an open finding is not anchored.
2
findings
The scope is fully covered and at least one finding is open (confirmed or unresolved).
1
clean
The scope is fully covered and no finding is open. It means "no open finding within a fully covered scope"; it is not a proof that the code is correct, and the command says so.
0
Findings use closed vocabularies: severity critical, high, medium or low; confidence high, medium or low; disposition confirmed, refuted or unresolved; ten categories (correctness, security, data-loss, concurrency, performance and others). An unknown value is an error, never coerced. A refuted finding keeps its quoted ground; a confirmed one keeps its evidence.
Anchoring compares whole consecutive lines after normalising whitespace. One match is located, several are ambiguous, none is unlocated. An open finding that is not located keeps the gate incomplete: requote a longer exact excerpt.
A review may record why it stopped (coverage.json field run.stop: completed, budget, time-limit, failure-streak, interrupted). Everything but completed keeps the gate incomplete, even if every file is listed: a stopped review is never clean. Three failed attempts in a row on the same unit abandon it for this review.
review continue refuses (exit 3, status moved) when the commit, the rules or a selected file changed; the answer is then a new scope and review compare. In compare, a finding is identified by its path, category and normalised quote, never by line or id, and a finding that disappeared counts as resolved only if the later review completed its file; otherwise it is "not reviewed".
Redaction floor: before findings.anchored.json is written, credential-like values in the quoted fields (private-key blocks, URL credentials, authorization headers, bearer tokens, values of credential-named keys, well-known token shapes) become [REDACTED]. It is a floor, not a scanner: a secret of no recognisable shape can pass.
A rule is a Markdown document bound to path patterns; checklist.md lists every rule that applies to at least one selected path, once, with the files it covers. A rule is a prompt to look, not a finding. A rule can name a group (a family) so that parallel reviewers take one unit per group.
The eight rules shipped with BMAD+
Rule
Group
Applies to
general
code
Every file: callers, removed behaviour, boundaries, errors, trust, tests.
javascript-typescript
code
JS, TS, JSX, TSX, Vue and Svelte files.
python
code
Python files.
sql-and-migrations
data
SQL and migration files.
shell
code
Shell scripts.
ci-workflows
delivery
CI workflow files.
containers
delivery
Container build files.
configuration
delivery
Configuration files.
A project adds, replaces or disables rules in _bmad/review-rules.yaml (schema bmad-plus/review-rules/1): a rule is {id, title, globs, doc} with a Markdown document inside _bmad/ of at most 64 KB, and disable: [id] removes one. Unknown keys, duplicate ids, a document outside _bmad/ and disabling an unknown rule are refused, never ignored. The rule set is hashed into the scope, so a findings file answers exactly one rule set. With the Shield pack installed, six compliance rules join: shield-access-control, shield-personal-data, shield-cryptography, shield-logging, shield-ai-integrations and shield-change-and-supply-chain. Each names the controls it relates to, such as ISO27001:A.8.5, SOC2:CC6.1, GDPR:Art.32 or NIS2:Art.21(2)(d).
Two copy-ready GitHub Actions workflows live in docs/recipes/ci-review/. review-scope.yml seals the scope and publishes the checklist in the job summary and as an artifact (no agent, no model). review-gate.yml seals the scope, has the host agent installed and signed in on a self-hosted runner write findings.json and coverage.json, checks that nothing but those two files changed in the review folder and nothing outside it in the checkout, then anchors and gates; the job fails on an incomplete review or open findings. Both run only for pull requests from the same repository, with read-only permissions and everything pinned. gate --emit-check writes a document whose github block has the shape of a check run, with at most 50 annotations on anchored lines (critical and high as failure, medium as warning, low as notice).
Use when the change is not committed yet or only one folder matters. --base HEAD --workspace compares the working tree, untracked files included, with the last commit, and --include narrows the scope.
In your AI assistant
Sentinel, review my uncommitted work in src/billing only. Seal the scope with bmad-plus review scope billing-1 --base HEAD --workspace --include "src/billing/**" --effort medium and apply the rules in the checklist. Quote the code of every finding word for word, account for every selected file in coverage.json, run review anchor and review gate, and tell me exactly which files were left out and why. If the gate says incomplete, say what is missing instead of calling it clean.
What you should get_bmad-output/review/billing-1/ holds scope.json and checklist.md, then findings.json, findings.anchored.json and coverage.json once the review is written. The report gives the verdict (incomplete, findings or clean) with the coverage figure, lists each open finding with its quoted code, and names the files left out: a .env next to the code is excluded as secret, a file outside src/billing as not-included.
Automated tests prove that the pieces behave. A recipe proves that a person saw the chain work on screen: writer, reader, screen, gesture. The Core skill bmad-plus-uat (driven by Sentinel; commands uat, alias recette) turns a delivery into a self-contained page a non-technical tester plays step by step. Their ticks become a JSON run that the agent reads, triages and gates on.
01
Write the spec
A JSON file _bmad-output/uat/specs/<product>-<version>.json (schema bmad-plus/uat-spec/2). One real sequence of steps, each with where, do and expect; one click per do line, one verifiable fact per expectation; on-screen labels word for word, copied from the code; a witness (a thing the test relies on) with the read-only query that proves its state; writes: true plus a verify check on every step that persists something.
02
Lint and build
uat lint checks labels against your source files, the markup, the budget (15 steps and 30 minutes by default) and the witnesses. uat build writes the page, which carries the spec’s SHA-256 so that a run always says which revision it answered.
In your project terminal
npx bmad-plus uat lint ID --src src
npx bmad-plus uat build ID
03
Deliver the page
Locally, uat serve listens on 127.0.0.1 only and writes the answers straight into the project at every tick. Elsewhere, send the HTML file; the tester saves the JSON and you load it with uat import. In Claude Code or claude.ai the page can be published as an artifact whose database keeps the answers. Say plainly where the answers live, and which steps write for real.
In your project terminal
npx bmad-plus uat serve ID --port 4173
04
Read the run
uat read prints, per run, the tester, the dates, the counts of passed, failed, blocked, skipped and to do, stale or unsigned warnings, and each failure with its expectation text and the tester’s note.
In your project terminal
npx bmad-plus uat read ID
05
Triage, confirm writes, gate
Classify every failed or blocked expectation in triage/<id>.json, run the verify of each passed writing step read-only and record it, then run uat gate.
In your project terminal
npx bmad-plus uat gate ID
lint, build, then a gate with no run yet (the shipped example spec)
$ npx bmad-plus uat lint example-1.0.0example-1.0.0: 3 steps, 5 expectations, 8 on-screen labels no error$ npx bmad-plus uat build example-1.0.0/work/demo/app4/_bmad-output/uat/pages/uat-example-1.0.0.html — 3 steps, 83 KiB, spec a4a4850b284dopens in English; the tester can switch to any of the 10 languages on the pagecarries the 17 page guarantees: hidden-before-start, no-answer-before-run, verified-local-write, honest-save-status, save-on-every-change, restore-before-capabilities, newer-copy-wins, revision-guard, other-tab-notice, unreadable-draft-kept, progress-accessible, storage-explained, finished-is-not-accepted, unload-guard, utf8-and-escaped-diacritics, export-is-the-run, no-third-party-requests$ npx bmad-plus uat gate example-1.0.0example-1.0.0: awaiting - no run yet# Exit code 2: awaiting. The spec file is the example shipped in the Core uat skill (templates/example-uat-spec.json).
passed: the run is finished, current, complete and triaged.
No failed step.
1
failed.
At least one failure.
2
awaiting: no finished run yet.
No run.
3
stale: the run answered another revision of the spec.
Stale.
Triage: classify every failure before fixing anything
Class
Sign
What follows
product
The screen differs from what the code was meant to render.
A fix task on the story, and a replay step in the next version’s recipe.
recipe
The screen shows the intended result; the expectation described it wrongly.
Amend the spec, rebuild (new fingerprint), replay the affected steps, and say no product change was needed.
data
The witness is no longer in the assumed state.
Re-run the witness query, reset or rename it, replay.
undecided
No note, or not attributable.
Ask the tester what they saw; never guess. The gate stays red.
A decision is recorded for each triaged failure: fix, amend-spec, remeasure, accept-risk (with a named owner) or ask-tester. verify checks have a kind: sql, http, command or manual. A tick on a writing step is not a write: its verify is run read-only and recorded in writeChecks, because a step ticked nine times out of nine once turned out to have written nothing. The gate states what it establishes: "human-observed: complete, current and triaged". It does not establish that the tester looked at the right place.
advisory builds and offers the page, never blocks. gate makes the delivery wait for a passing gate. off produces no recipe and the delivery report says so. A missing block means advisory.
granularity
delivery
One recipe per delivery.
environment
name and url empty
The test environment the recipe targets (asked at install).
BMAD+ applies the rule to itself. On 2026-09-16 a command (studio list) was found broken in a real installation, and had been for weeks. The test suite was green, the install contract passed and CI was green. What none of them did was install the product and use it the way a person does. Since 0.17.0, a version does not reach npm until a person installed the candidate in an empty folder, walked the recipe page, and every failure was classified. The 0.23.0 recipe was played in an empty project on 2026-10-18: 28 of 28 expectations passed, with each writing step confirmed read-only. A first run of that recipe had exposed an uninstall defect that left the copied design skills behind; it was fixed before the tag. The runs themselves stay on the maintainer’s machine, because they carry local paths; the changelog records the defect it found.
In the BMAD+ repository, npm run uat:spec fails when the version in package.json has no recipe, and npm run uat:gate fails unless the latest run is finished, answers the current revision, leaves nothing unanswered, has every failure classified with a decision and has every passed writing step confirmed read-only. A pre-push hook enforces both on a v* tag push.
A blocked expectation needs a replay or an explicit accept-risk decision by a named owner. A recipe ticked just to make the gate pass is worse than no recipe: if a step cannot be played, the answer is "blocked" with the reason.
Your project can do the same with two scripts shipped in the uat skill (templates/): uat-spec-present.mjs (a version with no recipe is refused, before the push) and uat-release-gate.mjs (exit 0 passed, 1 failed, 2 no finished run, 3 other revision, 4 the gate could not run, which is not a pass). BMAD+ installs no lifecycle hook, so these scripts are the layer an instruction or an agent cannot skip; wire them where they bite.
Prepare an acceptance page for a booking form on staging#
Use when a version reaches a staging site and a non-technical colleague must confirm it on screen. Prefer disposable test data: the step that creates a booking writes for real.
In your AI assistant
Sentinel, write the recette for the booking form we just delivered to staging. The tester is someone from the front desk, not a developer: one step per gesture, one verifiable fact per expectation, on-screen labels copied from the code. For the step that creates a booking, add the read-only query you will run afterwards to prove that the row exists. Run uat lint with --src src, then uat build, and show me the guarantees the build lists. Tell me the file or link, how long it takes, which steps write, and where the answers will be stored.
What you should get A spec in _bmad-output/uat/specs/, a built page in _bmad-output/uat/pages/uat-<id>.html, the lint line ("N steps, M expectations, K on-screen labels checked against F source files"), a verify query on the booking step, the list of page guarantees, and a plain statement of where the answers live. The page is not handed over if lint or build fails.
Use when the tester has finished or stopped. The result file is in _bmad-output/uat/results/<id>/, or imported with uat import.
In your AI assistant
Sentinel, read the latest acceptance run with uat read. Classify every failed or blocked expectation as product, recipe, data or undecided in the triage file and ask me about any you cannot attribute. Run the verify check of each passed writing step read-only and record the result. Then run uat gate and report its verdict, the tester, and what stays open. Do not mark any unperformed step as passed.
What you should get A triage file with one class and one decision per failure, writeChecks entries for the writing steps, and a gate line such as <id>: passed (run <runId> by <tester>) with exit code 0, or failed, awaiting or stale with the reasons. An undecided failure keeps the gate red until the tester answers.