Add a `workflow.route` customization (oneshot, full, auto; default auto)
to bmad-build and bmad-build-auto. A route named in the invocation
becomes `--set workflow.route=<value>` on the render command. Templates
render only the pinned route's continuation; auto renders both plus the
selection rule, byte-identical to before apart from reporting. Story
frontmatter gains `route_source` (pinned or auto), and a pinned run
halts when a resumed spec's recorded route differs.
A pinned oneshot run does what it was told, how it was told: the
mid-build stop conditions narrow to an intent gap. Work turning out
larger than expected is the routing call the pin already made, and no
longer stops the run. A hole in the intent still does, because the only
alternative is to guess: bmad-build asks the human, bmad-build-auto
halts blocked with `intent gap`.
Give templates a `halt(message)` function so they can reject values they
cannot act on; each build skill's workflow.md uses it to halt the render
on a route outside oneshot, full, auto instead of silently rendering the
auto path.
Replace the regex token substitution and the line-parsed bmad-if
directives in render_skill.py with Jinja2. Templates see config,
workflow, and snapshot(); undefined names, empty entry files, and
links to omitted sources halt; every value a render reaches and the
Jinja2 version key the generation. Migrate the five rendered skills'
sources to the new forms, keep their shipped output byte-identical,
and update the validator rule and the authoring docs.
Add repeatable --set key=value arguments and an --overrides <file.toml>
file to render_skill.py. Shipped defaults, project TOML, user TOML, the
invocation file, and command-line assignments resolve into one effective
customization using the existing structural merge; persistent files are
never written.
Two --set paths that are equal or nested halt as a caller error. The
renderer keeps no separate schema of customization keys: a --set path
must exist in customize.toml, and any invocation override that reaches
no token or condition halts. Values are validated where consumed.
Standalone [[bmad-if:path == literal]] / [[bmad-else]] / [[bmad-endif]]
lines select sections before token and link resolution. A secondary
file that filters to nothing is omitted from the snapshot; workflow.md
filtering to nothing is an error. Condition inputs join the generation
identity so identical output from different inputs still yields
distinct snapshots.
Document both interfaces, ignore the per-machine skills-lock.json, and
cover precedence, equivalent forms, isolation, conflicts, unused
overrides, malformed input, nested conditions, and snapshot reuse.
* refactor(retrospective): run through the skill renderer
Make bmad-retrospective a rendered skill like bmad-build and
bmad-walkthrough. SKILL.md becomes the render_skill.py launcher, and a
new workflow.md carries the goal, conventions, modes, activation,
inputs, and phase flow. Reference files are reached through
[[bmad-snapshot:...]] tokens, artifact paths use
{{.implementation_artifacts}}, {{.planning_artifacts}} and
{{.output_folder}}, and the finish section reads {workflow.on_complete}
at render time instead of calling resolve_customization.py. The bundled
scripts keep running from {skill-root}/scripts. The greeting step is
gone; its going-in-concerns prompt moves to the point where the epic is
fixed.
* feat(render): bind {skill-root} to the installed skill directory
Rendered skills can bundle scripts and other non-Markdown files that
the snapshot does not carry. Resolve {skill-root} in skill sources to
the absolute installed skill directory at render time, so no runtime
placeholder survives into the snapshot, and add the skill path to the
generation identity so the same sources installed at two locations
publish distinct generations. Customization values keep binding
{skill-root} to the snapshot. The renderer tests now cover all four
rendered skills and the retrospective's script paths.
* docs(skills): tidy activation prose after the greeting removal
Move the retrospective's going-in-concerns prompt above the sprint-mode
split so it applies in stories mode too, and drop the greet-step
mentions from the walkthrough customize.toml comments.
The root package.json, lockfile, .nvmrc, prettier ignore file and
.npmignore are gone. docs-site has its own package.json and lockfile
with the Astro, ESLint and Prettier dependencies, and its scripts run
relative to that directory. tools/quality.py, both workflows and the
docs all call npm inside docs-site.
stamp_release.py stamps only the 29 skill manifests now; the version
lives nowhere else on this branch. The tests for package stamping go
with it.
* build: replace the npm lint stack with pre-commit
markdownlint-cli2, prettier and eslint for YAML/JSON, husky and
lint-staged are gone. pre-commit runs ruff, rumdl (the same five
markdown rules), yamllint, yamlfix, the JSON checks, both validators
and pytest, at commit time and over the whole tree from
tools/quality.py, which is now what `npm run quality` calls. ESLint and
prettier stay only for the docs-site scripts and tests. quality.yaml
collapses to a Python job and a docs-site job.
yamlfix keeps one blank line and no document-start marker: its defaults
strip blank lines inside block scalars and add `---`, which the sprint
status scripts do not write back. The JSON hook keeps key order and
non-ASCII text.
* style: apply yamlfix and JSON formatting to the tree
Mechanical output of the new hooks; every file parses to the same data.
* docs: state what the commit hook covers
* refactor(docs-site): move the doc tooling out of tools/
The five doc scripts and their test exist because of Starlight: sidebar
order frontmatter, the rehype link convention, the built site, and the
Astro build itself. They now live under docs-site/scripts and
docs-site/test, next to the rehype plugins and the site tests, which
leaves tools/ Python-only. ESLint stops ignoring those two directories
and its script rules now target them; the Astro sources stay excluded.
* ci(docs): deploy the site from dev
main moves only by release fast-forward, so a docs deploy tied to it
would lag every doc change until the next release. Deploy on push to
dev instead; main receives the same tree later and needs no deploy.
* build: declare the Python toolchain in pyproject.toml
Pin the dev environment (pytest, pytest-xdist, pyyaml, ruamel.yaml, ruff)
with uv instead of a one-line --with list in package.json. The project is
marked non-packaged: the tree itself is what gets installed, and shipped
scripts keep their inline metadata. The validate CI job now runs on uv
alone, without Node, and gains a ruff check.
* style: apply ruff to tools and skill scripts
Mechanical output of ruff check --fix and ruff format under the new
config, plus four hand edits: explicit zip strictness in setup.py and
test_sprint_status.py, an f-string for the hex colour in brain.py, and a
noqa on the sys.path-dependent import in render_skill.py. No behaviour
change; 500 tests pass.
* build: declare Python 3.11 as the floor of every shipped script
Eighteen scripts declared 3.8 or 3.10 inline while ruff targeted 3.11,
so an autofix could introduce syntax those scripts did not advertise.
The lower floors were never reachable: every skill that carried one
also runs a 3.11 hub script, and uv fetches whatever interpreter a
script asks for. All inline metadata now says 3.11, and a test keeps
the inline floors equal to requires-python and the ruff target.
The Python rewrite of the file-ref checker ported the JS pattern verbatim,
including the `\\\\` that only matched the escaped `C:\\Users` form. Main
fixed the JS copy in #2794 before this branch deleted it; apply the same
pattern here so the rebase does not revert the fix.
Setup resolved config through the script already, but still wrote
core/config.yaml and bmm/config.yaml on every run. Nothing reads them.
Drop the writes and the code that existed only to build them: the
legacy_config projection threaded through materialize_bmad/stage_bmad,
without_manifest_answers, delete_path, stringify, and the whole YAML
parse/render/fill path. ensure_file now merges TOML only.
Existing files are left in place, like the other classic-installer
traces, and both paths join LEGACY_LEFTOVERS so doctor reports them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Sn1de7Zc9yWcJnJkE6QNis
setup.py drops build metadata when ordering versions, so 6.12.0+hotfix
compares equal to 6.12.0 and a release stamped that way is invisible to
every installed copy. The runbook warned against it; the stamper accepted
it. Reject it before anything is written.
The version rule is now "orderable and distinguishable", one notch
stricter than setup.py's orderability - the contract test pins that gap
and pins the premise it rests on.
The knowledge value named `reference/help.md`, but the bmad skill has no
reference/ directory — the file is references/help.md. Nothing ever resolved
the value, so the dangling pointer reached 30 manifests and the release
stamper's constant unnoticed.
setup and doctor rewrite _bmad wholesale via replace_dir, so running
them through a symlinked _bmad crashed mid-flight with a raw
IsADirectoryError from the backup rename. Reads through the symlink
are the point of symlinking and stay untouched; only the write paths
now fail fast, naming the link target and the --project-root to use
against the real installation. The doctor flow rejects in its
missing-_bmad pre-flight so --list-config-questions --doctor fails
before interrogating the user.
The seven standalone support skills that help.md lists as belonging to
no path and no stage — brainstorming, forge-idea, deep-recon,
advanced-elicitation, review, party-mode, customize — now carry
module = "toolbox"; the other 22 skills carry module = "method". The
module key names only the distribution grouping; the _bmad/bmm runtime
config path skills read is staged by setup.py independently of it.
The Claude marketplace and the Codex plugin lid leave this repo: both
plugin ecosystems are built by bmad-code-org/bmad-plugins from the
stamped manifests bmad-skills main serves, sharing one skills tree per
plugin. The stamper therefore stamps and validates only the manifests
(exact key set, known module, the one known update source, per-module
byte identity), and the runbook gains a rebuild-the-plugins step.
Two marketplace entries share source "./" and list their skills
explicitly, replacing the single plugin whose skills tree was a symlink
to skills/ — with per-plugin subsets the symlink bought nothing. The
plugins/bmad-method lid is deleted. bmad-tools has no skills yet, and an
empty skills list makes Claude Code fall back to scanning skills/, so
its source points at the empty plugins/tools directory instead.
The stamper now validates the whole distribution shape before writing:
every manifest carries exactly module, version, and update_source, with
a known module and the one known source; the marketplace has exactly
one entry per module; and each entry's skills list equals, as a set,
the skills whose manifest carries that module — no orphans, no
double-shipping, no dangling paths (a dangling path is load-bearing:
Claude Code silently falls back to shipping every skill for one). The
manifest byte-identity check is now per module, since manifests
legitimately differ in the module line.
Verified live: bmad-bmm installs with 29 skills, bmad-tools with 0,
and a scratch stamp of the real tree passes.
The stamper globbed for manifests, so a skill directory without
module-manifest.toml was silently skipped and would ship unstamped
with no version or update_source. Enumerate skill directories and
fail naming the offender before anything is written.
The tools/bmm split will put a second plugin into the Claude marketplace,
so the stamper no longer hardcodes plugins[0] and the bmad-method path.
It now stamps the version of every entry in the marketplace plugins
array, discovers plugins/*/.claude-plugin/plugin.json by glob, and
requires the marketplace entry count to match the plugin manifest count.
Each textual rewrite is proven against the parsed JSON: the result must
equal the original tree with only the intended version nodes changed,
and a "version" key anywhere else in a stamped file fails the run
before anything is written.
The version's single source of truth is skills/*/module-manifest.toml.
Plugin metadata (Claude marketplace and plugin.json, Codex plugin.json)
is no longer stamped — whatever builds those artifacts reads the version
from skills/bmad/module-manifest.toml. The textual single-"version"-key
rewrite would have broken the moment the Claude marketplace gained a
second plugin, and those lids are likely to leave this repo anyway.
Tests updated: JSON-stamping cases replaced with a regression test that
plugin metadata is left byte-identical; runbook expectation adjusted.
Make the bmad-skills mirror's main branch release-only: dev now mirrors
this branch unstamped, and main is always current dev plus one stamp
commit cut by following tools/release.md.
- tools/stamp_release.py: stdlib-only stamper that validates an
orderable SemVer argument (rejects -dev, which setup.py cannot
order), rewrites the version line in every skills/*/module-manifest.toml
textually to keep the manifests byte-identical, and sets the version in
.claude-plugin/marketplace.json, plugins/bmad-method/.claude-plugin/
plugin.json, and .codex-plugin/plugin.json without reformatting them.
Nothing is written unless every file validates; after writing it
re-reads everything and fails naming the offending path if the
manifests diverge or any file carries a different version.
- tools/release.md: self-contained runbook — clean detached checkout of
origin/dev, human-chosen version that differs from what main serves,
stamp, review, commit, force-with-lease push of HEAD:main.
- tools/tests/test_stamp_release.py: unit tests covering the happy path,
invalid and -dev versions, missing version line/key, divergent
manifests, idempotent re-stamp, and formatting preservation.
- TEMP-RELEASE-PROCESS.md: the mirror is the testbed for both the npx
install/update flow and the release process that feeds it. Records the
dev/main branch model, the 0.0.0-next.N throwaway versions to stamp
there, and a note to replace the file with the real release
instructions before this branch merges to main.
- AGENTS.md: replace the test-mirror section with a pointer to that file.
- .npmignore: exclude the new tool files and the temporary runbook from
the npm tarball.
Replace tools/validate-skills.js with a stdlib Python 3.11 port and rewire
validate:skills and test:skills onto it. Observable behavior is preserved,
including the JS frontmatter quirks; in-skill file walks are sorted.
The checker's V4/V5 grammar (exec attrs, invoke-task, step metadata,
Load directives, quoted dot-paths, {_bmad} shorthand) matches nothing
in the flat tree; it verified only four script filenames. It now
resolves backticked slash-paths against the containing file's directory
and the skill root, flagging a missing file only when the path's first
directory exists — paths without one are prose, so the current tree
stays at zero false positives (276 refs verified, up from 135). Files
sitting directly under skills/ are reported as stray.
The checker still scanned src/, which the flattening removed, so it
crashed on any fresh checkout and failed the quality gate. It now scans
skills/; the obsolete core-skills/bmm-skills module mapping is replaced
by _bmad/scripts/ -> skills/bmad/scripts/, with the install-only and
install-generated skip lists unchanged.
Move the duplicated BMM routing guide into the bmad hub skill and replace
every module-manifest.md with a two-key TOML file parsed by stdlib tomllib.
Drop PyYAML from setup.py and from test:npx-skills.
Stdlib zipfile replaces the zip-CLI dependency; behavior and output
are otherwise unchanged. Verified live on both the success path (all
6 bundles packaged) and the missing-directory refusal path.
Move core and BMM skills to skills/<id>/ so npx skills can install
the repo as-is. Copy the BMM module manifest into every skill, move
the shared Python runtime into skills/bmad/scripts/, drop src/, and
retarget repo tests at the new locations.
Port tools/validate-file-refs.js to tools/validate_file_refs.py (PEP 723,
pyyaml) preserving detection, resolution, CLI, and CI-annotation behavior.
CSV extraction is dropped: .csv leaves the scan set, and the CSV test and
fixtures are deleted rather than ported. The npm dependencies block (yaml,
csv-parse) goes away with its sole consumer; validate:refs and test:refs
now run via uv, and test:refs joins the quality chain. A new stdlib
unittest suite covers extraction, mapping, skip lists, UNRESOLVED vs
BROKEN, leak detection, exit codes, and the GitHub Actions output path.
The port also hardens what the JS left fragile: multi-document YAML is
scanned in full, non-UTF-8 bytes are replaced instead of crashing the
run, and directory symlinks are not traversed.
Core skills are independent tools, not a set to finish. Help no longer
reports uninstalled skills as missing, and the packager no longer
requires every skill id in the manifest body.
The model already knows who it is talking to and what language fits the
conversation; answers frozen at install time only mislead. Setup asks no
questions of its own now — the only questions come from installed module
manifests. user_name, communication_language, user_skill_level, and
document_output_language leave the config templates, setup.py, the setup
reference, and every live skill's prose. Classic-installer surface
(module.yaml, tools/installer, v6-shims, bmad-help) is unchanged; the
test suite keeps negative assertions that the keys stay out of team
config.