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.
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.
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
Action
What it does
Writes
lint
Validates 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
build
Lints first and refuses on any error, then writes the tester page.
<dir>/pages/uat-<id>.html
serve
Serves 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
import
Stores a results JSON exported from the page (file or standard input).
<dir>/results/<id>/<runId>.json
read
Lists every run and each failed or blocked expectation with its text and the tester note.
nothing
gate
Derives the verdict: passed, failed, awaiting or stale. --emit-check also writes a self-contained verifier.
<dir>/checks/gate-<id>.cjs (only with --emit-check)
order
Computes the play order of all recipes in <dir>/specs from their declared after and shared witnesses.
<dir>/ORDER.md
Options
Flag
Argument
Default
What it does
-d, --directory
<path>
current directory
Project directory.
--dir
<path>
_bmad-output/uat
Recipe folder inside the project: specs/, pages/, results/, triage/, checks/.
--src
<path>
none
Source folder scanned for on-screen labels (lint, build). Repeatable.
--language
<code>
see below
Page language, as a code (fr), a name (Français) or a locale tag.
--input
<file>
none
import only: results file to store; - reads standard input. Required for import.
--port
<number>
4173
serve only: port, bound to 127.0.0.1 only.
--emit-check
none
off
gate only: also write the Nexus verifier checks/gate-<id>.cjs.
--json
none
off
Machine-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.
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.
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
Class
Meaning
Usual decision
product
The screen differs from what the code was meant to render.
fix
recipe
The screen is right, the expectation described it wrongly.
amend-spec
data
The witness is no longer in the assumed state.
remeasure
undecided
No note, or not attributable: ask the tester. Keeps the gate red.
lint error, template lost a guarantee, or any failure
-
-
import
run stored
any failure
-
-
read
runs read, nothing failed
a failed or blocked expectation, or any failure
no run yet (awaiting)
newest run answered another revision (stale)
gate
passed
failed (or any failure)
awaiting
stale
order
order written
a 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.
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.0example-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 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 lint example-1.0.0 --src srcexample-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.0demo-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)
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.
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).