This repo is a BMAD standalone module (one skill + a Claude marketplace.json). The skill
(auto-bmad) is an orchestrator that runs the BMAD build lane (bmad-build-auto plan → build →
follow-up review, plus risk-gated TEA and epic-boundary work) one story at a time — or, with
/auto-bmad epic, a whole epic in one run (references/epic-pipeline.md) — on Claude Code, Codex,
or opencode. This file is guidance for working on the module, not for using it.
The orchestrator delegates BMAD work and reports — it never runs /bmad-* skills itself (the
only exception is the inline tier), never reads or edits story code, and never edits the spec
(spec metadata comes only from story_plan.py --spec / --find-spec). Every BMAD step — the
bmad-build-auto plan run, build run and follow-up review pass, TEA, the retrospective, the
deferred-work reconcile — runs in a delegated generic subagent at the phase profile's model.
Preserve this separation when editing.
The orchestrator owns a small set of actions directly (never delegated) — git/finalize
bookkeeping it already holds full pipeline context for. git-and-pr.md → "Ownership" is the
single normative list; in short:
- Git/PR work — preflight, branching, per-phase commits, push, PR, CI wait, the Phase 9 merge
prompt, and the clean-tree gate before every build-auto invocation (build-auto HALTs on a dirty
tree and authors every code commit itself; the orchestrator never makes a
featcommit). - Phase 0 probes —
preflight.py(git state/mode,uv, Python 3.11, nested-subagent capability,_bmad/config.toml,AGENTS.mdblock, required skills), the config-drift heal (config_plan.py), and the review-layers TOML sync (build_auto_custom.py). - Sprint-status write-back around build-auto (sprint mode only — in stories mode there is no
write-back at all: build-auto owns the story-file status) —
story_plan.py --mark-status: Phase 3 end →ready-for-dev, Phase 5 start →in-progress(+ the epic lift), Phase 5 end →review, the Phase 8 pre-retrodoneflip, the Phase 9doneflip. Scripted, never trusted to a delegate. - Phase 7 halt handling — external-review changes are detected with a git-only check (never a code read), committed, and the halt re-opened once; the re-review is delegated (one more build-auto follow-up pass), never an inline read.
- Phase 8 deferred-work archive (+ the Phase 7 tail harvest of the spec's
deferred:list into<impl>/deferred-work.md) —deferred_ledger.pymechanics; the keep-vs-move judgment and thedeferred-reconciledelegate stay LLM/delegated. - Phase 9 finalize writes — the pre-push pipeline-report commit + the status flip.
- The retro verdict gate ask (Phase 0 / E0) when the previous epic's newest retro doc says
rejected.
auto-bmad's review extras are two [[workflow.review_layers]] tables (auto-bmad-security,
auto-bmad-cross-model) that build_auto_custom.py writes into a marker-fenced managed region of
the project's _bmad/custom/bmad-build-auto.toml — build-auto runs them inside its own review step;
the orchestrator never fans out reviewers itself. Keep assets/bmad-custom/bmad-build-auto.toml in
lockstep with upstream bmad-build-auto/customize.toml's layer schema (compat-check "critical") —
it holds one @@VARIANT:<schema>@@ branch per upstream diff-placeholder line (diff_output ≤ 6.11.0,
diff_file ≥ 6.11.1) and the script picks the branch the installed skill uses.
The mechanics live in the reference docs — don't restate them here: git-and-pr.md
(ownership, branching, push, PR, merge prompt), pipeline.md (Phase 0 probes, status write-back,
Phase 7 halt + tail, Phase 8 archive, Phase 9), delegation.md (the per-step prompts).
BMAD abstracts neither sub-agent delegation nor per-agent model/effort, so we supply those through each host's native subagent mechanism — no rendered agent files, nothing provisioned per tool, nothing to go stale:
- Tier 1
subagents(Claude Code, Codex, opencode) — a generic subagent spawned with the phase profile's model: Claude Code Agent toolmodel:(per-call model; effort inherits the session), Codex per-callmodel+reasoning_effort(the only host honoring per-phase effort in-tool), opencode default subagent (inherits the user's model and reasoning). Spawned in the foreground, one step at a time. - Tier 2
inline— no subagent mechanism; run the step in-context (documented last resort).
Orthogonal to the tiers, an opt-in per-phase external-CLI route (delegation.cli_phases) sends
a phase to claude -p / codex exec / opencode run for cross-tool/cross-vendor diversity. It
reads the same profiles blocks (claude→--effort, codex→model_reasoning_effort,
opencode→--variant; opencode's model + variant are optional ⇒ inherit), is still delegation (the
orchestrator builds the command + parses the result, never reads code), hard-stops on a failed
preflight (binary/skills/auth), and needs no in-tool nesting when both build and
followup_review are routed. The per-tool flag matrix + validation live in
scripts/cli_delegate.py (tested), not orchestrator prose. Default empty ⇒ all in-tool.
Nesting requirement. build-auto spawns its own subagents, so the subagents tier needs
orchestrator (depth 0) → delegate (depth 1) → build-auto's subagents (depth 2). Preflight verifies
it per host (knobs in "Known platform facts") and prints a verbatim fix on hard_stop.
assets/profiles.yaml is the single source of truth — per-profile, per-tool model + effort
only (claude.model/effort, codex.model/reasoning_effort, opencode.model/variant; no
persona text — every delegation.md prompt carries its own role line); phase_profiles maps the
ten phase keys to a profile. Host/mode are auto and re-detected every run. Full detail:
delegation-runtime.md (host detection, nesting, the tiers), cli-route.md (the cli_phases
route) and state-and-resume.md (config/profiles schema).
.claude-plugin/marketplace.json— Claude distribution (lists the single./auto-bmadskill).auto-bmad/SKILL.md— orchestrator entry point (On-activation gate + procedure). Keep it thin.auto-bmad/references/— where the real detail lives; each file owns one area:pipeline.md— per-phase (per-story) playbook.epic-pipeline.md— the/auto-bmad epicE-step flow (the per-story phases are the inner loop; no per-story halts).delegation.md— exact per-step prompts (tool-agnostic, self-contained).delegation-runtime.md— host detection, nesting, the two spawn tiers.cli-route.md— the opt-incli_phasesexternal-CLI route (resolver, argv, launch/wait, result contract, cross-model layer shapes). Loaded only whencli_phasesis non-empty.stories-mode.md— thebmad-specspec-folder story source (stories.yamlfolder+id dispatch): route selection, naming, status mapping, per-phase + E-step deltas, checkpoints. Loaded only whenstory_source == stories.tea-policy.md— TEA risk rubric / selection.git-and-pr.md— ownership list, branching, commits, push, PR, merge prompt.state-and-resume.md— config/state schema, profiles, removed-keys note, resume, reports.config-commands.md— the config commands (first-run flow,reset-defaults,config-check)- the shared drift-report rendering. Loaded only for a config command or the Phase 0 pause.
auto-bmad/assets/profiles.yaml— the single per-profile source (model/effort blocks + thephase_profilesmap). Custom profiles (any name) are first-class:config_plan.py's heal passes them through, a whole-block reset prunes them.auto-bmad/assets/config-defaults.yaml— the source of truth for the constant-default setup-block keys (delegation/tea/git/code_review/build) that the Phase 0 drift heal appends to an existingconfig.yaml. Deliberately omits environment-detected/interviewed fields (git.base_branch,host/mode,tea.enabled/framework_ci,git.mode,code_review.cross_model_layer) so the append-only heal can never write a wrong static value. Lockstep: each default must equal the orchestrator fallback and thestate-and-resume.mdschema (config_plan.py's--self-testenforces the include/exclude sets).auto-bmad/assets/bmad-custom/bmad-build-auto.toml— the review-layers region template (@@…@@placeholders filled bybuild_auto_custom.py). Mirror upstream's layer schema.auto-bmad/assets/module.yaml+module-help.csv+module-setup.md— module identity, help rows, and the self-registration/provisioning flow (help catalog + review-layers sync).auto-bmad/scripts/— dependency-free helpers, each with a--self-testand a self-documenting docstring (read the script for exact behavior):story_plan.py— the single story-source adapter:--resolve(explicit--storyarg → key/title, ambiguity hard-stop),--epic N(enumerate keys/statuses/first-last),--mark-status(byte-preserving BMAD-status flip +last_updatedstamp + epic lift,[a-z]?split-key grammar),--find-spec/--spec(build-auto spec discovery + frontmatter/## Auto Run Resultreader),--retro-verdict(retro-doc frontmatter).state_plan.py—state/{key}.yamlreader (resume detection,--scope epic);--finalizeevaluates the Phase 9 draft predicate / clean-completion verdict.state_update.py— deterministic per-story state/report writer (init / patch / phase-done, timing brackets, literal report sections). Lockstep-self-tested against the schema block.config_plan.py— detects and additively heals drift between the shipped defaults (profiles.yaml;config-defaults.yaml) and a project's runtimeconfig.yaml. Append-only (--apply);--resetre-seeds the profiles blocks (whole-block scope prunes stale profiles / keys) but never the setup blocks.preflight.py— one-call Phase 0 preflight (--central-config-onlyfor the gate): central TOML read (tomllib),uv+ Python 3.11, nesting, git state/mode,AGENTS.md, CI, required skills +sprint_plan.pylocation, framework detection — one JSON with hard-stop reasons.deferred_ledger.py—harvest(Phase 7 tail: specdeferred:→ ledger, idempotent),plan/archive(Phase 8, sha-guarded atomic move); keep-vs-move judgment stays with the LLM.cli_delegate.py— resolves the opt-in CLI route for a phase (argv + model/effort +launch_cmd, livevalidate(),--once/--waitwatchers) and--layer-argvbuilds the ONE shell line of the cross-model review layer thatbuild_auto_custom.pybakes into the TOML.ci_wait.py— Phase 9 CI wait: pollsgh pr checks, classifiesci_status, resolvesci_run_urlby head SHA.build_auto_custom.py— syncs the managed review-layers region of_bmad/custom/bmad-build-auto.tomlfrom the runtime config (--check/--apply; models baked fromprofiles; the diff-placeholder variant detected from the installedbmad-build-auto/customize.toml; whole-filetomllibvalidation; duplicate-id guard). Setup,reprovision, Phase 0 freshness.merge-help-csv.py— the live self-registration merge into_bmad/_config/bmad-help.csv(anti-zombie; stdlib only, no PyYAML). Called with--target; never--legacy-dir(it would delete the per-module file setup just wrote).merge-config.py— retained only to satisfy the standalone-module validator (validate-module.pyrequires the file); auto-bmad does not invoke it — it never writes the installer-owned central BMAD config. Don't reintroduce a call to it.- Both of the above are VENDORED, not ours: verbatim copies from
bmad-code-org/bmad-builder(skills/bmad-module-builder/assets/standalone-module-template/), MIT © BMad Code, LLC. Each carries a provenance header with origin, sync SHA, and local-delta list — record every local edit there, or the next re-sync silently reverts it. Prefer fixing upstream and re-syncing. The upstream lives inbmad-builder, a separate repo fromBMAD-METHOD.
- Repo-root tooling, NOT shipped in the skill:
CHANGELOG.md(hand-maintained),scripts/bump-version.py(release helper — see "Releasing"),skills/reports/(tracked module-validation snapshots),docs/(the v7 migration plan + the upstream capability backlog).
# Deterministic cores:
python3 auto-bmad/scripts/story_plan.py --self-test
python3 auto-bmad/scripts/state_plan.py --self-test
python3 auto-bmad/scripts/state_update.py --self-test
python3 auto-bmad/scripts/preflight.py --self-test
python3 auto-bmad/scripts/config_plan.py --self-test
python3 auto-bmad/scripts/cli_delegate.py --self-test
python3 auto-bmad/scripts/ci_wait.py --self-test
python3 auto-bmad/scripts/deferred_ledger.py --self-test
python3 auto-bmad/scripts/build_auto_custom.py --self-test
# Maintainer-only skill (tracked under .claude/ via gitignore exception; NOT shipped to users):
python3 .claude/skills/auto-bmad-compat-check/scripts/bmad_compat.py --self-test
# Marketplace manifest is valid JSON:
python3 -m json.tool .claude-plugin/marketplace.json >/dev/null
# Module structure passes the BMAD validator (run from the repo root, which holds the one skill):
python3 .claude/skills/bmad-module-builder/scripts/validate-module.py .
# Live: add this repo as a local marketplace (Claude) or BMAD module source, install, run
# /auto-bmad in a BMAD project. `/auto-bmad reprovision` re-syncs the review-layers TOML.
# Release helper:
python3 scripts/bump-version.py --self-testThe version lives in four tracked files that must stay in lockstep —
.claude-plugin/marketplace.json (version), auto-bmad/assets/module.yaml (module_version),
the README shields badge, and auto-bmad/references/state-and-resume.md
(profiles_source_version, the config.yaml schema example). "Publishing" is just pushing a
vX.Y.Z git tag (the BMAD
installer keys upgrade detection off stable tags; the Claude plugin marketplace reads the manifest
version).
Cut a release from a clean main:
- Ensure this release's notes are under
## [Unreleased]inCHANGELOG.md, grouped under Keep-a-Changelog headings. Write them by hand as changes land — never auto-generate from commits. python3 scripts/bump-version.py <patch|minor|major>(or an explicitX.Y.Z;--dry-runto preview). It refuses an empty[Unreleased], guards against version drift across the four files, promotes the changelog (date + compare links), rewrites all four versions, then commitschore(release): vX.Y.Zand tags it.git push --follow-tags.
.github/workflows/release.yml then fires on the v* tag and creates the GitHub Release from that
tag's CHANGELOG section (idempotent; it verifies the tag agrees with all four version files and the
changelog first). That's the only CI — no build/publish step (/auto-bmad reprovision is a runtime
concern, not a release artifact).
- Conventional Commits (
feat:/fix:/docs:/test:/chore:/refactor:). - Never commit the local BMAD test install or any host config —
_bmad/,_bmad-output/,.agents/,.claude/,.codex/,.opencode/are gitignored. The published repo is module + marketplace + docs only. One deliberate exception (a.gitignorenegation): the tracked maintainer skill.claude/skills/auto-bmad-compat-check/— checks new releases (npmlatest/next) of both packages auto-bmad delegates into —bmad-method(its "critical" contract owners arebmad-build-autoincl. thecustomize.tomlreview-layer schema,bmad-sprint-planning,bmad-retrospective) and the separately versionedbmad-method-test-architecture-enterprise(TEA, thebmad-testarch-*skills) — for impact on auto-bmad and offers to bump the README compat markers. It derives auto-bmad's skill surface from thebmad-…tokens inSKILL.md+references/*.md, so never spell a removed or shim skill name in a shipped file (thestate-and-resume.mdremoved-keys note, self-test fixtures andCHANGELOG.mdare the only sanctioned places). Repo tooling, not shipped inside the module; everything else under.claude/stays ignored. - Markdown reference files are read by the orchestrator at runtime; keep them concise and
unambiguous (they are instructions, not prose). Helper scripts stay dependency-free with a
--self-test. - Don't land a user-facing change without a
CHANGELOG.mdnote under## [Unreleased](right Keep-a-Changelog heading) in the same commit/PR. Never bump the version files by hand — usescripts/bump-version.pyso all four stay in sync (see "Releasing"). - Changelog entries are written to be skimmed. A reader must grasp a release from the bold
lead lines alone, in seconds. Enforce:
- One change = one bullet under one heading. Never bundle (if you're writing "three reinforcing fixes", that's three bullets).
- Bold headline first, ≤ ~12 words, stating the user-visible effect ("X no longer Y"), not the internal mechanism.
- At most ~2 sentences of detail after the headline — the one fact a reader needs. No "Previously…/the gap was…/chicken-and-egg" debugging narrative; the how lives in the reference docs and the commit body.
- Hard cap: 2 wrapped lines per bullet (3 only for a major item) — headline, detail, and trailing parenthetical all included. Over the cap? Cut detail, never the wrap width.
- No inline file-touch lists — git history records touched files. If a pointer genuinely
helps, one terse trailing parenthetical (
(pipeline.md, git-and-pr.md)), never woven into sentences.
- Past released
## [X.Y.Z]sections are immutable — apply this style to new entries only; never rewrite a shipped section (release.ymlrenders the GitHub Release from it).
- Claude Code (2.1.232): the Agent tool takes a per-call
modeland no per-call effort (effort inherits the session — per-phase effort on Claude is CLI-route/cross-model-layer only). Subagents nest to depth 3 by default (≥ 2.1.219): envCLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTHwins when set; non-integer or< 1values are ignored (⇒ default),=1disables nesting — so preflight hard-stops only on a parsed1. Since 2.1.198 subagents run in the background by default — build-auto needs synchronous spawns, so the orchestrator spawns every delegate in the foreground (run_in_background: false) and every delegate prompt mandates foreground spawns for build-auto's own subagents.claude -pflags used:--model,--effort(low|medium|high|xhigh|max),--output-format json|text,--allowedTools,--dangerously-skip-permissions. - Codex (0.147.0):
spawn_agenttakes a per-callmodel+reasoning_effort(gpt-5.x: low|medium|high|xhigh — the only host with per-phase effort in-tool). Nesting: V1[agents] max_depthdefaults to 1 (child depth > max_depth is rejected ⇒ a delegate can't spawn build-auto's subagents;max_depth = 2fixes it), ignored underfeatures.multi_agent_v2(V2 has no depth guard). Config layering: project<repo>/.codex/config.tomloverrides~/.codex/config.toml($CODEX_HOME);-c key=valueoncodex execoverrides both (CLI route only). These keys are source-verified (0.147.0), not in the public docs.codex execflags used:-m,-c model_reasoning_effort=E,-c approval_policy=never,-s read-only,-C,-o,--ephemeral,--dangerously-bypass-approvals-and-sandbox(-ais a top-levelcodexflag only). Model names are environment-specific — config, not hardcoded. - opencode (1.18.15): the Task tool has no per-call model/effort knob (only
subagent_type, plus resume/background flags ⇒ a delegate inherits the user's model;opencode.model/variantmatter only on the CLI route and the cross-model layer). Nesting: top-levelsubagent_depthinopencode.json(default 1 — subagents can't spawn subagents; auto-bmad needs2), and a subagent getspermission.taskdenied unless its agent definition grants it (agent.<name>.permission.task: allow). Background subagents are opt-in (synchronous by default).opencode runflags used: prompt as the final positional arg (not stdin),-m provider/model,--variant,--format json(a JSONL event stream — parsed defensively bycli_delegate.extract_opencode_result),--dir,--auto(auto-approve permissions). Skills load from.opencode/skills/,~/.config/opencode/skills/and the.claude/.agentsskills/**/SKILL.mdroots. opencode injectsOPENCODE_SESSION_IDinto the shell env of commands it runs (host-detection signal). - BMAD (6.11.x) has no portable abstraction for delegation or model/effort; modules are skills
the installer copies into
.claude/skills/(Claude Code) or.agents/skills/(Codex, opencode — the cross-tool standard; the installer warns about stale.codex/skills//.opencode/skills/installs as legacy paths). The CLIs still load skills from more roots (seedelegation-runtime.md), which is why preflight probes several dirs. Hence the tiered design. - BMAD
bmad-build/bmad-build-autorender throughuv run --no-cache _bmad/scripts/render_skill.py(PEP 723requires-python >= 3.11) and HALT whenuvis unavailable — souv+ a Python ≥ 3.11 reachable byuvare hard prerequisites (preflight P3/P4;uv python find '>=3.11' --no-python-downloadsis the side-effect-free probe). auto-bmad's ownpython3must also be ≥ 3.11 (tomllib). - BMAD central config is TOML — auto-bmad READS it, never writes it. Four layers resolved
highest-last:
_bmad/config.toml→config.user.toml→custom/config.toml→custom/config.user.toml(tables deep-merge; arrays-of-tables merge bycode/id— a repeated id replaces).preflight.py --central-config-onlymirrors that merge withtomllibforcore.output_folder+ the BMM artifact paths — nothing else in auto-bmad reads the central BMAD TOML (build_auto_custom.pyvalidates our own customization file, and preflight's nesting probe reads codexconfig.toml). The only BMAD config file auto-bmad writes is the fenced managed region of_bmad/custom/bmad-build-auto.toml(help-catalog rows and thestory_plan.py --mark-statussprint-status flip aside; skill customization resolves<skill>/customize.toml→_bmad/custom/<skill>.toml→<skill>.user.toml,[[workflow.review_layers]]merged byid). The installer still writes per-module_bmad/{code}/config.yamland other skills (e.g.bmad-sprint-planning) still load_bmad/bmm/config.yaml— preflight warns when it is missing (P11). A--custom-sourceinstall ofabmregisters[modules.abm]inconfig.toml+_bmad/abm/config.yaml— leave both alone. - Help catalog on 6.11:
bmad-helpreads_bmad/_config/bmad-help.csv, which the installer regenerates on every install/update by merging every_bmad/<module>/module-help.csv(installer.js mergeModuleHelpCatalogs;_config/custom/scripts/renderdirs skipped). So setup copies our rows to_bmad/abm/module-help.csv(survives re-installs) and merges them into the live catalog; the legacy shared_bmad/module-help.csvis read by nothing upstream. - v6 shims: the installer classifies a skill as a shim by
metadata.lifecycle: shimin its SKILL.md frontmatter (shim-policy.js); a fresh install ships none unless--shims(--no-shimsforces them off). Never require or name a shim skill. - BMAD
--custom-sourceinstall (howabminstalls): a bare URL clones the default branch HEAD and recordschannel: next; the version selector is an@<tag-or-branch>suffix (…/auto-bmad@v0.20.1⇒channel: pinned; raw SHAs unsupported) —custom-module-manager.js parseSource/cloneRepo(cloneRepotakes no channel argument). So the README's bare install/update commands trackmainHEAD — pin with@<tag>for a release. Cache:~/.bmad/cache/custom-modules/. - BMAD update of a custom-source module (
abm, 6.11):--action quick-updatenow refreshes URL-backed cached custom repos before re-deploying (findModuleSourceByCode … _refreshRepoCacheOnce→cloneRepo(rawInput, pinOverride):next⇒ latest default-branch commits,pinned⇒ the pinned ref) — but only for modules found in that cache; a marketplace-installed / self-registeredabmwith no cache entry is skipped ("no source available"). Re-supplying the source —npx bmad-method install --action update --custom-source <repo-url> --yes— works in every case, so the README "Updating" section recommends it. - Sprint-status grammar (
bmad-sprint-planning/scripts/sprint_plan.py): story keys match^(\d+)-(\d+)([a-z]?)-.+(the[a-z]?is the split suffix, e.g.2-6a-…); epics headingsEPIC_RE/STORY_RE(Story (\d+)\.(\d+[a-z]?)) —story_plan.pymirrors both exactly; thelast_updatedstamp format is%m-%d-%Y %H:%M. Statuses:backlog|ready-for-dev|in-progress|review|done. bmad-build-autocontracts are prose, parse defensively: the HALT protocol writes the spec frontmatterstatus+ a## Auto Run Resultblock withStatus:/Blocking condition:lines (a no-spec HALT writes abmad-build-auto-result-*.mdskeleton instead);Halt after planning.is the "standard phrasing" that stops after step 2 atready-for-dev(build-auto accepts "any clear equivalent"); a dirty tree,no subagents,intent gap,blocked spec suppliedetc. areblockedblocking conditions. build-auto commits its own diff and never pushes.story_plan.py --specis the only reader.- Shell globs: the orchestrator's probe commands run under whatever shell the host uses (zsh,
fish, bash). An unmatched glob is fatal in zsh/fish (
nomatch⇒ exit 1), and thefor f in *.glob; do …loop syntax isn't even portable to fish — so probes must not iterate raw globs. Usefind … -name '<pat>'(external binary, empty output + exit 0 everywhere) or Python.