# bmad-plus review

> Reference for bmad-plus review: seal a review scope, anchor findings on the code, derive a verdict from coverage, resume, compare and list rules, with flags and exit codes.

Canonical: https://bmad-plus.rochetta.fr/docs/reference/cli-review/
Language: en
BMAD+ version: 0.23.1
Documentation reviewed: 2026-10-19
Author: Laurent Rochetta
License: MIT

BMAD+ documentation › [Reference](https://bmad-plus.rochetta.fr/docs/reference/) › bmad-plus review

`bmad-plus review` is the evidence layer of a code review. It does not read your code with a model and it does not judge it: the assistant (usually Sentinel) does the reading. The command fixes what is being reviewed (the scope), checks that every quoted finding really sits in the code, and derives the verdict from what was actually covered, so a review that stopped halfway can never be reported as clean. Use it when you want a review whose result you can check, script or gate a pull request on.

> **Note · What clean means** `clean` means: no open finding within a fully covered scope. It does not prove the code correct, and a review with zero findings is allowed. Missing, failed or invalid evidence gives `incomplete`, never `clean`.

<a id="synopsis"></a>
## Synopsis

**In your project terminal**

```sh
npx bmad-plus review <action> [id] [options]
npx bmad-plus review rules [path]
npx bmad-plus review compare <id> --since <earlier-id>
```

`id` names a review and becomes a folder: lowercase letters, digits, dots and dashes, starting with a letter or digit, at most 81 characters (for example `pr-42`). `rules` is the only action that takes a path instead of an id.

**The six actions**

| Action | What it does | Writes |
| --- | --- | --- |
| `scope <id>` | Seals what is reviewed: the changed files between two commits (or the working tree), which of them are selected, which rules apply to each, and the review plan. | `scope.json`, `checklist.md` |
| `anchor <id>` | Validates `findings.json`, then locates every quoted excerpt in the code at head. Credential-like values are replaced by `[REDACTED]` in the written record. | `findings.anchored.json` |
| `gate <id>` | Derives the verdict from the scope, the coverage and the anchored findings: `clean`, `findings` or `incomplete`. | optionally a CI check file |
| `continue <id>` | Lists what an interrupted review still owes, against the same sealed scope. Refused when the code or the rules moved. | `continue.json` |
| `compare <id> --since <earlier-id>` | Sorts this review’s findings against an earlier review: new, persisting, resolved, refuted, not reviewed. | `compare.json` |
| `rules [path]` | Prints the effective rule set, or the rules a given path would get. | nothing |

**Options of review**

| Flag | Argument | Default | What it does |
| --- | --- | --- | --- |
| `-d, --directory` | `<path>` | current folder | Project folder. |
| `--dir` | `<path>` | `_bmad-output/review` | Review folder inside the project. Each review lives in `<dir>/<id>/`. |
| `--base` | `<ref>` | `HEAD~1` (`HEAD` with `--workspace`) | Base commit or branch. The scope starts at the merge base of base and head. |
| `--head` | `<ref>` | `HEAD` | Head commit or branch. |
| `--workspace` | - | off | Review the working tree against the base, untracked files included. |
| `--include` | `<glob>` | none | Only review matching paths. Repeatable. |
| `--exclude` | `<glob>` | none | Never review matching paths. Repeatable. |
| `--effort` | `low\|medium\|high` | `medium` | Review depth (see below). |
| `--since` | `<id>` | none | `compare` only: the earlier review to compare against. Required there. |
| `--emit-check` | `<file>` | none | `gate` only: also write the verdict as a JSON check result for CI. Must be a `.json` file. |
| `--json` | - | off | Machine-readable output instead of text. |

<a id="workflow"></a>
## The order of work

1. **Seal the scope** `review scope <id>` writes `scope.json` and a checklist of the rules that apply to the changed paths. The scope has a digest: any later change of code or rules is detectable.

   ```sh
   npx bmad-plus review scope pr-42 --base main --effort high
   ```
2. **Review and write the evidence** The assistant reads the files, writes `findings.json` (each finding quotes its code verbatim, with category, severity, confidence and disposition) and `coverage.json` (every selected file ends as `completed`, `failed` or `waived` with a reason). BMAD+ never calls a model for this.
3. **Anchor the findings** `review anchor <id>` finds each quoted excerpt in the code. Line numbers written by a model are never used; only the quote is.

   ```sh
   npx bmad-plus review anchor pr-42
   ```
4. **Ask for the verdict** `review gate <id>` derives `clean`, `findings` or `incomplete`. Add `--emit-check` to get a JSON document a CI step can consume.

   ```sh
   npx bmad-plus review gate pr-42 --emit-check ci/review-check.json
   ```
5. **Resume or compare when needed** `review continue <id>` after an interruption; `review compare <new-id> --since <id>` after the code changed and you reviewed again.

<a id="reads-writes"></a>
## What it reads and writes

The command reads the git history and files of the project (it starts `git` for `rev-parse`, `diff`, `merge-base` and `ls-files`) and the review rules. It writes only inside `<dir>/<id>/`, plus the `--emit-check` file. It opens no network connection and calls no model.

**Files in the review folder**

| File | Written by | Content |
| --- | --- | --- |
| `scope.json` | `scope` | Base, head, merge base, selected and excluded files with reasons, rules per file, units, plan, digest. |
| `checklist.md` | `scope` | The rules that apply to the changed paths, as a checklist for the reviewer. |
| `findings.json` | the assistant | Schema `bmad-plus/review-findings/1`; each finding quotes `existing_code` verbatim. |
| `coverage.json` | the assistant | One item per selected file (`completed`, `failed`, `waived` with a reason) and how the run ended (`run.stop`). |
| `findings.anchored.json` | `anchor` | The findings with their located lines and counts of located, ambiguous and unlocated. |
| `continue.json` | `continue` | The packet of remaining work. |
| `compare.json` | `compare` | The new, persisting, resolved, refuted and not-reviewed findings. |

<a id="finding-vocabulary"></a>
### Closed vocabularies

**Allowed values in findings.json and coverage.json**

| Field | Values |
| --- | --- |
| `severity` | `critical`, `high`, `medium`, `low` |
| `category` | `correctness`, `security`, `data-loss`, `concurrency`, `performance`, `error-handling`, `compatibility`, `test-gap`, `maintainability`, `documentation` |
| `confidence` | `high`, `medium`, `low` |
| `disposition` | `confirmed`, `refuted`, `unresolved` |
| coverage `outcome` | `completed`, `failed`, `waived` |
| `run.stop` | `completed`, `budget`, `time-limit`, `failure-streak`, `interrupted` |

An unknown value is an error, never coerced. A refuted finding must keep its refutation, a confirmed one needs evidence, a failed or waived file needs a reason. Only `run.stop: completed` lets the gate reach a verdict; any other reason keeps it `incomplete`. A unit that fails three attempts in a row is abandoned and stays failed.

<a id="effort"></a>
## Effort levels and the plan

**--effort**

| Level | Passes | Refutation pass |
| --- | --- | --- |
| `low` | 1 | no |
| `medium` (default) | 2 | yes |
| `high` | 3 | yes |

A pass is a complete read of the scope; later passes look for what earlier ones missed. The plan printed by `scope` also depends on size: from 150 changed lines the reviewer plans before reading, and with more than one unit and at least 400 changed lines the units can be reviewed in parallel. A unit groups at most 8 files and 400 lines.

<a id="exclusions"></a>
### Files that are never selected

- `secret`: `.env*`, keys and certificates (`*.pem`, `*.key`, `*.p12`, `*.pfx`, `id_rsa*`, `id_ed25519*`), `.npmrc`, `.netrc`, and anything under `credentials/`, `.credentials/` or `secrets/`. Checked before any `--include`, so no pattern can bring them in.
- `binary`, `deleted`, `excluded-by-pattern` (your `--exclude`), `not-included` (outside your `--include`).
- `generated-or-vendored`: `node_modules`, `dist`, `build`, `coverage`, `vendor`, minified files, source maps and lockfiles, unless an `--include` pattern matches them.
- The review folder itself: writing findings never changes the scope they answer.

<a id="rules"></a>
## Review rules

A rule is a Markdown checklist chosen by file path. Every rule whose patterns match a file applies to it, so rules add up. Eight ship with the CLI.

**Built-in rules**

| Rule id | Group | Applies to |
| --- | --- | --- |
| `general` | code | every file |
| `javascript-typescript` | code | `js cjs mjs jsx ts cts mts tsx vue svelte` |
| `python` | code | `py pyi` |
| `sql-and-migrations` | data | `*.sql`, migration folders, `*.prisma`, `schema.rb` |
| `shell` | code | `sh bash zsh ps1 psm1`, `Makefile` |
| `ci-workflows` | delivery | GitHub Actions, GitLab CI, Bitbucket and Azure pipelines |
| `containers` | delivery | `Dockerfile`, `Containerfile`, compose files, Kubernetes and Helm manifests |
| `configuration` | delivery | `json yml yaml toml ini properties`, `package.json`, `pyproject.toml`, `requirements*.txt`, `go.mod`, `Cargo.toml`, `composer.json` |

When the Shield pack is installed, six compliance rules are added (they never replace or disable a built-in one): `shield-access-control`, `shield-personal-data`, `shield-cryptography`, `shield-logging`, `shield-ai-integrations` and `shield-change-and-supply-chain`. Each names the controls it examines as FRAMEWORK:ID, for example `ISO27001:A.8.5`, `SOC2:CC6.1`, `NIST-800-53:AC-3` or `NIS2:Art.21(2)(i)`, and a finding may cite only controls that the rules for its file examine.

A project adds, replaces (same id) or disables rules in `_bmad/review-rules.yaml`. Its `doc` files are Markdown, resolved next to that file. Disabling an unknown id is an error.

**_bmad/review-rules.yaml**

```yaml
schema: bmad-plus/review-rules/1
rules:
  - id: billing-money
    title: Money amounts
    group: code
    globs: ['src/billing/**/*.ts']
    doc: review-rules/billing-money.md
disable:
  - python
```

<a id="exit-codes"></a>
## Exit codes

**Exit codes by action**

| Action | Code | Meaning |
| --- | --- | --- |
| `scope`, `rules`, `compare` | 0 | Done. |
| `anchor` | 0 | Every finding is located. |
| `anchor` | 1 | `findings.json` is invalid, or at least one finding is ambiguous or unlocated: quote a longer, exact excerpt. |
| `gate` | 0 | `clean`. |
| `gate` | 1 | `findings`: at least one confirmed or unresolved finding is open. |
| `gate` | 2 | `incomplete`: a file is unaccounted for or failed, the evidence is invalid or missing, the run stopped early, or an open finding is not anchored. |
| `continue` | 0 | A packet of remaining work was written. |
| `continue` | 3 | `moved`: the code or the rules changed since the scope was sealed (or `coverage.json` is invalid). |
| any action | 3 | Error: unknown action, bad id, missing scope or findings, git failure, unreadable or invalid JSON. |

<a id="real-output"></a>
## Sample output

These transcripts are simulated: the lines follow the message templates of the command, the numbers and the digest are illustrative. The paths printed by the real command are absolute.

**Seal a scope** (Simulated)

```text
$ npx bmad-plus review scope pr-42 --base main --effort high
<project>/_bmad-output/review/pr-42/scope.json
3 file(s) to review, 41 changed lines, 1 unit(s) — scope 3f9a1c0d52b7
  excluded 1 × generated-or-vendored
checklist: <project>/_bmad-output/review/pr-42/checklist.md
plan: high effort, 3 pass(es), refutation pass
Every selected file must end in coverage.json as completed, failed or waived (with a reason).
```

Illustrative transcript, not captured from a real run. Source: tools/cli/commands/review.js (scope action message templates)

**A gate that is not ready** (Simulated)

```text
$ npx bmad-plus review gate pr-42
pr-42: incomplete — coverage 1/3 completed (0 waived), 0 open finding(s), 0 refuted
  - 2 selected file(s) never accounted for: src/a.js, src/b.js
# Exit code 2. The review owes two files.
```

Illustrative transcript, not captured from a real run. Source: tools/cli/commands/review.js (gate action) and tools/cli/lib/review.js (reviewGate reasons)

**A clean verdict** (Simulated)

```text
$ npx bmad-plus review gate pr-42 --emit-check ci/review-check.json
pr-42: clean — coverage 3/3 completed (0 waived), 0 open finding(s), 0 refuted
  check: <project>/ci/review-check.json
  clean: no open finding within a fully covered scope. It does not prove the code correct.
# Exit code 0.
```

Illustrative transcript, not captured from a real run. Source: tools/cli/commands/review.js (gate action)

The `--emit-check` document is stable (no timestamp, so the same evidence gives the same bytes). It carries the status, exit code, reasons, counts, the scope it answers, the open findings with their lines and a `github` block shaped like the check-runs API: `conclusion` (`success` or `failure`) and an `output` with a Markdown summary and up to 50 annotations. A working-tree review writes its check inside the review folder or outside the project, so it cannot enter the change it reports on.

<a id="errors"></a>
## Errors and what to do

Errors print `review: <message>` on the error stream (or a JSON object with `status: "error"` under `--json`) and exit 3.

**Messages**

| Message | Cause | What to do |
| --- | --- | --- |
| `unknown action "x" (scope, anchor, gate, continue, compare, rules)` | The action is not one of the six. | Use one of the listed actions. |
| `a review id is required: lowercase letters, digits, dots and dashes` | No id, or an id with other characters. | Pass an id like `pr-42`. |
| `no scope at <path> — run "bmad-plus review scope <id>" first` | The review folder has no `scope.json`. | Run `review scope <id>` with the same `--dir`. |
| `invalid ref "..."` or `git rev-parse failed: ...` | A `--base` or `--head` that does not resolve to a commit. | Check the ref (fetch it in CI); refs starting with `-` are refused. |
| `git is unavailable: ...` | git is not on the path of the process. | Install git or fix the path. |
| `effort must be one of low\|medium\|high` | Unknown `--effort`. | Use `low`, `medium` or `high`. |
| `no findings at <path>` | `anchor` or `compare` found no `findings.json`. | Write it, even with an empty list. |
| `--since <earlier review id> is required: ...` / `--since must name a different review` | `compare` without `--since`, or against itself. | Pass the earlier review id. |
| `--emit-check writes a .json file` / `--emit-check must not overwrite the review evidence` | The check path is not a `.json` file, or is one of the evidence files. | Choose another `.json` path. |
| `<id> cannot continue: the sealed scope no longer describes the code.` | Exit 3 from `continue`: head moved, files changed or rules changed. | Seal a new scope with the same options, then `review compare <new-id> --since <id>`. |
| gate reasons: `no coverage.json: ...`, `no findings.json: ...`, `N selected file(s) never accounted for: ...`, `the review stopped before the end (budget): ...` | The evidence is missing, partial or the run stopped early. Exit 2. | Finish the review (`continue`), write both files, set `run.stop` to `completed` only when it really did. |

<a id="examples"></a>
## Examples

<a id="example-cli-review-seal-and-gate"></a>
### Review a pull request with checkable evidence

You want Sentinel to review branch changes against main and to report a verdict you can trust.

**In your AI assistant**

```text
Sentinel, review this branch against main. If the BMAD+ CLI is available, seal the scope with bmad-plus review scope pr-42 --base main --effort high, quote every finding verbatim, then anchor and gate it, and report the gate answer without upgrading it.
```

**What you should get** Sentinel runs `review scope`, which writes `scope.json` and `checklist.md` under `_bmad-output/review/pr-42/`. It reviews the selected files, writes `findings.json` and `coverage.json`, runs `review anchor` (exit 1 if a quote cannot be located) and `review gate`. It reports `clean`, `findings` or `incomplete` exactly as the gate printed it. A stopped or partial review is reported as incomplete, never as clean.

<a id="example-cli-review-resume"></a>
### Resume an interrupted review

The session ended before every file was read and coverage.json records run.stop as interrupted.

**In your AI assistant**

```text
Sentinel, continue the review pr-42 where it stopped. Use bmad-plus review continue pr-42 and only do what it lists.
```

**What you should get** `review continue` writes `continue.json` and prints the files and units still to review, the findings to requote, the passes left and any unit abandoned after three failed attempts. If the code or the rules moved, it refuses with exit 3 and tells Sentinel to seal a new scope and use `review compare`. When nothing remains, it says to run `review gate` once `coverage.json` records the run as completed.

<a id="example-cli-review-compare"></a>
### Review again after fixes

The author pushed fixes. You sealed a new scope pr-43 and finished the second review.

**In your AI assistant**

```text
Sentinel, compare review pr-43 with pr-42 using bmad-plus review compare pr-43 --since pr-42 and tell me what is new, what persists and what is really resolved.
```

**What you should get** The command prints `pr-43 since pr-42: N new, N persisting, N resolved, N refuted, N not reviewed` and writes `compare.json`. A finding from the earlier review that sits on a file the new review did not complete is counted as not reviewed: it is not known to be fixed.

<a id="example-cli-review-rules"></a>
### See which rules apply to a file

You wonder what Sentinel will check in an authentication file when Shield is installed.

**In your AI assistant**

```text
Sentinel, run bmad-plus review rules src/auth/login.ts and tell me which rules and which compliance controls apply.
```

**What you should get** The command prints the rule set digest and the layers it was built from (built-in, pack files, project file), then `src/auth/login.ts gets N rule(s)`, one line per rule with its id, group, layer and patterns, and for Shield rules the controls such as `ISO27001:A.8.5` and `SOC2:CC6.1`. Rules a project disabled are listed as disabled. Nothing is written.

<a id="ci"></a>
## Using it in CI

The repository ships copy-ready GitHub Actions recipes in `docs/recipes/ci-review/`: `review-scope.yml` seals the scope and publishes the checklist (no agent, no model), `review-gate.yml` has the host agent installed on a self-hosted runner write the evidence, verifies that nothing outside the review folder changed, anchors the findings and fails the check on `incomplete` or open findings. They run only for pull requests from the repository itself, never from forks, and need no model key stored in GitHub. In a script, the three exit codes are enough: 0 passes, 1 means findings to read, 2 means the evidence is not there yet.

**See also**

- [Exit codes of every command](https://bmad-plus.rochetta.fr/docs/reference/cli-overview/#exit-codes)
- [assurance: evidence cases for Shield](https://bmad-plus.rochetta.fr/docs/reference/cli-assurance/)
- [uat: human acceptance recipes](https://bmad-plus.rochetta.fr/docs/reference/cli-uat/)
- [nexus: durable runs and independent verification](https://bmad-plus.rochetta.fr/docs/reference/cli-nexus/)
- [Troubleshooting](https://bmad-plus.rochetta.fr/docs/faq/troubleshooting/)

[BMAD+ documentation](https://bmad-plus.rochetta.fr/docs/) · [Reference](https://bmad-plus.rochetta.fr/docs/reference/)

[Source on GitHub](https://github.com/lrochetta/BMAD-PLUS)
