Browse the documentation

bmad-plus uat

Reference for bmad-plus uat (alias recette): lint, build, serve, import, read, gate and order human acceptance recipes, with flags, exit codes and sample output.

On this page
  1. Synopsis
  2. What it reads and writes
  3. How the gate decides
  4. Exit codes
  5. Sample output
  6. Errors and what to do
  7. Examples

bmad-plus uat (alias bmad-plus recette) handles human acceptance recipes. A recipe, called a recette in French, is a JSON spec of steps that a person plays on screen. The command lints the spec, builds one self-contained HTML page for the tester, stores the tester's answers as a JSON run, lists the failures and derives a gate verdict from that run. Use it when a delivery must be seen working by a person and not only by tests. Sentinel drives it for you; you need the command to script the process, to wire a CI step or to check what the agent claims.

Note What the gate does and does not prove

The gate establishes that a run is finished, current, fully answered and triaged. It never establishes that the tester looked at the right place, so every verdict is labelled human-observed. A gate that says passed can still sit on top of failures: they only have to be classified, each with a decision.

Synopsis

In your project terminal
npx bmad-plus uat <action> [target] [options]
npx bmad-plus recette <action> [target] [options]

action is one of the seven actions below. target is a recipe id (the command looks for <dir>/specs/<id>.json, then for any spec in that folder whose id equals it) or the path of a .json spec file. order takes no target.

The seven actions
ActionWhat it doesWrites
lintValidates the spec: schema, unknown keys, markup, step and expectation ids, witnesses, the 15-step / 30-minute budget. With --src, checks that every quoted on-screen label exists in a source file.nothing
buildLints first and refuses on any error, then writes the tester page.<dir>/pages/uat-<id>.html
serveServes the built page on 127.0.0.1 and stores every answer the page sends. Runs until you press Ctrl+C.<dir>/results/<id>/<runId>.json
importStores a results JSON exported from the page (file or standard input).<dir>/results/<id>/<runId>.json
readLists every run and each failed or blocked expectation with its text and the tester note.nothing
gateDerives the verdict: passed, failed, awaiting or stale. --emit-check also writes a self-contained verifier.<dir>/checks/gate-<id>.cjs (only with --emit-check)
orderComputes the play order of all recipes in <dir>/specs from their declared after and shared witnesses.<dir>/ORDER.md
Options
FlagArgumentDefaultWhat it does
-d, --directory<path>current directoryProject directory.
--dir<path>_bmad-output/uatRecipe folder inside the project: specs/, pages/, results/, triage/, checks/.
--src<path>noneSource folder scanned for on-screen labels (lint, build). Repeatable.
--language<code>see belowPage language, as a code (fr), a name (Français) or a locale tag.
--input<file>noneimport only: results file to store; - reads standard input. Required for import.
--port<number>4173serve only: port, bound to 127.0.0.1 only.
--emit-checknoneoffgate only: also write the Nexus verifier checks/gate-<id>.cjs.
--jsonnoneoffMachine-readable output, with schemaVersion: 1.

Page language, in order of priority: the language field of the recipe, then --language, then the communication_language read from _bmad/config.yaml, then English. The page always embeds the ten installer languages (en, fr, es, de, pt-br, ru, zh, he, ja, it) and the tester can switch on the page.

What it reads and writes

  • Reads: the specs in <dir>/specs (refused above 4 MiB or when the file is a symbolic link), _bmad/config.yaml (build only, for communication_language), the results in <dir>/results/<id>/ and the triage file <dir>/triage/<id>.json.
  • With --src: reads source files with these extensions only: ts, tsx, js, jsx, mjs, cjs, vue, svelte, html, htm, php, py, rb, java, kt, cs, go, json, yaml, yml, md. It skips symbolic links, dot folders, node_modules, .git, dist, build, coverage, .next, .nuxt, vendor, __pycache__, .venv, venv, target and out, ignores files above 512 KiB and stops after 20000 files.
  • Writes: only the files named in the table above. Credential-like values pasted in a tester note are replaced by [REDACTED] before a run reaches the disk (import and serve); the run then records how many replacements were made in redactions.
  • Network: none, except serve, which listens on 127.0.0.1 only. The built page itself asks nothing of any other origin. A tester on another machine uses the page as a file or as an artifact and sends back the JSON for import.
  • serve answers only to the host names 127.0.0.1:<port> and localhost:<port> (any other Host header gets HTTP 403), refuses a request body above 4 MiB, and answers HTTP 409 when an older copy of a run would replace a newer one.

How the gate decides

The gate takes the most recently finished run of the recipe. If there is none, the status is awaiting. If that run answered another revision of the spec (its specSha256 differs from the current spec), the status is stale. Otherwise the status is failed as long as at least one reason below applies, and passed when none does.

  • An expectation was never answered (optional steps are skipped).
  • A failed or blocked expectation has no entry in <dir>/triage/<id>.json, or its class is undecided, or its decision is not one of fix, amend-spec, remeasure, accept-risk, ask-tester.
  • A decision is accept-risk without decidedBy.
  • An expectation is blocked and its decision is not accept-risk: nothing was observed, so replay it or have someone accept the risk by name.
  • A step with writes: true passed, but the triage has no writeChecks entry with verified: true and an evidence text: a tick on a writing step is an observation, the read-only proof settles it.
Triage vocabulary
ClassMeaningUsual decision
productThe screen differs from what the code was meant to render.fix
recipeThe screen is right, the expectation described it wrongly.amend-spec
dataThe witness is no longer in the assumed state.remeasure
undecidedNo note, or not attributable: ask the tester. Keeps the gate red.ask-tester

Exit codes

Exit code by action
Action0123
lintno errorat least one error, or any failure--
buildpage writtenlint error, template lost a guarantee, or any failure--
importrun storedany failure--
readruns read, nothing faileda failed or blocked expectation, or any failureno run yet (awaiting)newest run answered another revision (stale)
gatepassedfailed (or any failure)awaitingstale
orderorder writtena cycle, no spec in the folder, or any failure--

A failure is any thrown error: unknown action, missing target, spec not found, unreadable file, missing --input. It prints uat: <message> on standard error, or {"schemaVersion":1,"status":"error","message":...} with --json, and exits 1. A release script that must tell a failed gate (1) from a gate that could not run uses the project gate uat-release-gate.mjs, which adds exit 4 for the second case.

Sample output

The first block was replayed with the example spec that ships in the bmad-plus-uat skill; the read and gate blocks are composed from the message templates of the command, flagged as simulated, and reuse the fixture of tests/unit/uat-cli.test.js. File counts, sizes and hashes depend on your project.

Lint then build a recipe
npx bmad-plus uat lint example-1.0.0
example-1.0.0: 3 steps, 5 expectations, 8 on-screen labels
  no error
npx bmad-plus uat build example-1.0.0
/work/app/_bmad-output/uat/pages/uat-example-1.0.0.html — 3 steps, 83 KiB, spec a4a4850b284d
opens in English; the tester can switch to any of the 10 languages on the page
carries 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 lint example-1.0.0 --src src
example-1.0.0: 3 steps, 5 expectations, 8 on-screen labels
  error   step 0-setup: do: the label "Folders" is in no source file — copy it from the component, accents included or not
  error   step 1-nominal: where: the label "Technical" is in no source file — copy it from the component, accents included or not
Four more errors of the same kind follow and the command exits 1: here `src` is an empty folder, so none of the spec’s labels is found. `--src` must point at the folders that really hold your screens’ labels.

Output captured from a real run. Source: Replayed with the 0.23.0 package and the example spec of the bmad-plus-uat skill, in a throwaway project; the project path is replaced by /work/app

Read a run, then ask for the gateSimulated
npx bmad-plus uat read demo-1.0.0

■ Demo 1.0.0 — Sarah — 2026-09-16 08:00, finished
  1 passed · 1 failed · 0 blocked · 0 skipped · 0 to do   (_bmad-output/uat/results/demo-1.0.0/20260916-sarah-aaaa.json)
  ✗ 1-confirm/a (failed) — Confirm
      expectation: The line reads OPCO confirmed.
      seen instead: the line still reads OPCO to confirm
Exit code 1: a failure exists.
npx bmad-plus uat gate demo-1.0.0
demo-1.0.0: failed (run 20260916-sarah-aaaa by Sarah)
  - 1-confirm/a is failed and not triaged
Exit code 1. Once the failure is classified in triage/demo-1.0.0.json (class product, decision fix), the same command prints “demo-1.0.0: passed ...” and exits 0.

Illustrative transcript, not captured from a real run. Source: tools/cli/commands/uat.js (read and gate templates); tests/unit/uat-cli.test.js (fixture demo-1.0.0, run 20260916-sarah-aaaa)

Errors and what to do

Messages printed by uat
MessageCauseWhat to do
uat: an id or a spec file is requiredNo target given to an action that needs one.Pass the recipe id or the path of the .json spec.
uat: no spec with the id "<id>" in <dir>/specsNo file in the specs folder carries that id.Check --dir and --directory; the match is on the id field, not only the file name.
step <id>: ... or unknown key at spec level: "<key>" ... (listed as error)A typo or a missing field in the spec: unknown keys are refused, not ignored.Fix the spec and run lint again.
the label "<text>" is in no source fileA quoted on-screen label is not found in the folders given with --src.Copy the label from the component, accents included or not. Without --src this check does not run.
<id>: not built, N error(s)build runs the lint and refuses to write the page on any error.Fix the listed errors.
the page template ... lost a guarantee — not builtThe page template no longer carries one of the 17 guarantees.Reinstall or update BMAD+; do not edit the template by hand.
no page at <file> — run "bmad-plus uat build <id>" firstserve needs a built page.Run build, then serve again.
--input <file> or --input - is requiredimport without --input.Pass the exported JSON or - for standard input.
these results answer <a>, not <b>The imported run belongs to another recipe.Import it with the id it answers.
unsupported results schema: ... or results without runIdThe file is not a run exported by the page.Export again from the page, or use the artifact export.
<id>: no run yet (exit 2)No JSON in <dir>/results/<id>/.Serve or send the page, then import the answers.
unknown action "<x>" (lint, build, serve, import, read, gate, order)Mistyped action.Use one of the seven actions.

Examples

Have Sentinel write and build the recipe

A version is ready for the test environment. You want a page a non-technical colleague can play.

In your AI assistant
Sentinel, write the acceptance recipe for this delivery on our test environment. Lint it against src, build the page and tell me which steps write for real.

What you should get Sentinel writes the spec to _bmad-output/uat/specs/<id>.json, runs bmad-plus uat lint <id> --src src and fixes what it reports, then runs bmad-plus uat build <id>, which writes _bmad-output/uat/pages/uat-<id>.html and lists the page guarantees. It hands you the link or the file, the duration, and the steps marked writes: true, each with its read-only verify check. If the CLI is not available it says so instead of claiming the page was built.

Read the run, triage, ask for the gate

The tester has finished the page and sent back the JSON.

In your AI assistant
Sentinel, import the results file results.json for recipe demo-1.0.0, read the run, classify every failure and tell me whether the gate passes.

What you should get Sentinel runs uat import demo-1.0.0 --input results.json, then uat read demo-1.0.0, which prints each failed or blocked expectation with its text and the tester note. It writes a class and a decision for each one into _bmad-output/uat/triage/demo-1.0.0.json, asks you when a note does not explain the failure (class undecided keeps the gate red), and finally reports uat gate demo-1.0.0 with the status and the exit code (0 passed, 1 failed, 2 awaiting, 3 stale). It labels the evidence human-observed.

Wire the gate into a release script (project gate copied from templates/)
node scripts/uat-release-gate.mjs || exit 1
# deploy only below this line

The skill ships two project gates in .agents/skills/bmad-plus-uat/templates/: uat-spec-present.mjs fails when no recipe names the version in package.json, and uat-release-gate.mjs runs uat gate with the CLI installed in the project (exit 0 passed, 1 failed, 2 no finished run, 3 another revision, 4 the gate could not run, which is not a pass).