Skip to content

feat!: distribute BMAD as a flat skills tree via npx skills and plugin marketplaces - #2768

Open
alexeyv wants to merge 90 commits into
mainfrom
feat/npx-skills-distribution
Open

feat!: distribute BMAD as a flat skills tree via npx skills and plugin marketplaces#2768
alexeyv wants to merge 90 commits into
mainfrom
feat/npx-skills-distribution

Conversation

@alexeyv

@alexeyv alexeyv commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Why

Every agent that understands skills already has an install door: npx skills add, and for Claude Code and Codex, plugin marketplaces. BMAD's skills could not go through any of them — they lived nested under src/, depended on an installer-built _bmad/, and shipped through a bespoke npm installer that only some agents could use.

This PR makes the repository itself the distributable: a flat skills/ tree that generic tooling can install, with a small bmad hub skill handling project setup, update checks, and repair. The classic installer and its packaging pipeline are removed.

What changed

Flat skills tree. All 29 skills now live at skills/<canonical-id>/, each carrying a module-manifest.toml (module membership, version, update_source, and a knowledge key pointing at the bmad skill's help doc). No build step stands between the repo and an install.

Two modules, one flat tree. The seven standalone support skills that belong to no path and no stage — brainstorming, forge-idea, deep-recon, advanced-elicitation, review, party-mode, customize — plus the bmad hub carry module = "toolbox"; the other 21 carry module = "method". The key names only the distribution grouping; the _bmad/bmm runtime config path skills read is staged by setup.py independently of it.

The bmad hub skill. Answers help from the module manifests (falling back to the remote docs site for questions the local manifests cannot answer) and owns three commands: bmad setup (materializes _bmad/ — scripts, config, output dir — and fills new config keys on re-run without touching custom/ or *.user.toml), bmad update (checks every installed skill's manifest against its update_source, reporting plugin-shipped copies as plugin-managed), and bmad doctor (repairs an existing runtime). Deterministic parts are Python driven by setup.py.

Distribution split across two repos. This repo is the source of the skills tree only. bmad-code-org/bmad-skills serves it to installers — dev mirrors this branch, main is release-only and defines the version installed copies compare against. The Claude and Codex plugin ecosystems are built separately by bmad-code-org/bmad-plugins from the stamped manifests bmad-skills main serves, so no .claude-plugin, .codex-plugin, or plugins/ directory remains here. All three doors — npx skills add, the Claude marketplace, the Codex plugin — were exercised end to end against that arrangement.

Release process. tools/release.md is a hand-run runbook and tools/stamp_release.py a deterministic stdlib-Python stamper: the human supplies a SemVer version, the stamper writes it into every manifest and validates them (exact key set, known module, the one known update source, per-module byte identity), and the runbook turns that into one stamp commit force-pushed to main plus a tag, then a plugin rebuild. dev is never stamped.

Removals. The classic installer (tools/installer), the npx packager and publish scripts, the npm publish workflow, the v6 shim skills, and assorted dead tooling. Net effect: 430 files changed, +12,182 / −36,562 lines.

Tooling ported to Python. validate_skills, validate_file_refs, and the renderer snapshot tests are Python now; tests are colocated with the skills they cover and run in npm test.

Repo housekeeping. CLAUDE.md is a tracked one-line import of AGENTS.md so both agent files stay identical; website/ renamed to docs-site/; every version declaration aligned at 6.11.0-next.

Still draft because

  • User-facing docs (README.md, docs/how-to/install-bmad.md, and translations) still document the removed npx bmad-method install flow and need a rewrite around the new doors.
  • TEMP-RELEASE-PROCESS.md describes the rehearsal against the disposable mirror and must be replaced with the real process before merge.

Replaces #2736, whose description predated most of this scope.

@alexeyv
alexeyv force-pushed the feat/npx-skills-distribution branch from 69481ae to 9254832 Compare August 21, 2026 02:54
@alexeyv
alexeyv marked this pull request as ready for review August 24, 2026 17:14
@greptile-apps

greptile-apps Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review (434 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@github-actions

Copy link
Copy Markdown
Contributor

@coderabbitai review

Walk core+bmm skill sources into a skills/<canonical-id>/ tree and
fatten only dest bmad-help with the shared Python, module.yaml defaults,
and a baked core+bmm help catalog.
Read top-down from main, drop PackagerError, and stop catching
failures so a missing source or payload surfaces as a stack.
Read package steps top-down, assemble bmad-help.csv through the
stdlib csv module so quoted descriptions stay intact, and assert
bad, empty, or short catalogs fail without touching dest.
Materialize _bmad from dest help: scripts symlink or copy,
shipped module.yaml defaults, empty custom/, baked catalog.
First-run setup copies authored team and user templates instead of
walking module.yaml, and reads the three user answers from a sidecar
so the skill route does not put them on the shell.
Setup is the only place that knows the template filenames; the packager copies help assets as-is.
Invoke setup from {skill-root}/scripts/setup.py so dest scripts
includes the pump and an npx update is seen. Require
resolve_config.py in the payload.
Move the setup interview and pump into references/setup.md
so ordinary bmad-help loads do not pay that context.
A second bmad-help setup now fills new team-config and module-yaml
keys, replaces the baked catalog, and repairs a wrong or stale
scripts path. Existing custom/ and *.user.toml stay untouched.
Document shipped skill relationships, conditional routes, completion
criteria, and artifact destinations for independently packaged skills.

Refs: sc-1
Add the canonical bmad entrypoint, exclude bmad-help from flattened
output, and retain transitional CSV packaging. Move the setup tests,
allow the canonical root ID in validation, and normalize the existing
install-only custom path for strict reference checks.
Validate module manifests and package versions before replacing output.
Copy identical manifests and declared scripts into every owning skill.
Replace catalog-driven ordinary help with fresh host skill discovery and
read-only manifest reasoning for partial and conflicting installations.
Add two deterministic runtime modes to the bmad skill. Update inspects
every installed manifest copy, compares each against its declared source,
and reports state per module without writing anything. Doctor repairs an
existing _bmad: it asks only newly declared questions, refreshes the
shared scripts as an exact copy, and makes each selected module's script
tree match its declaration, choosing the unique highest orderable release
when copies disagree and blocking the module otherwise.
The branch parked the npx setup payload in bmad-help and later moved it
to the bmad skill, leaving SKILL.md routing setup and update to files
that no longer exist. bmad-help is legacy-installer surface: this branch
leaves it byte-identical to main and the packager already excludes it.
alexeyv and others added 29 commits September 1, 2026 09:50
Point both marketplaces at plugins/bmad-method. That folder holds the
host manifests and a skills/ symlink into the repo skills tree so an
install copies the plugin directory and Claude dereferences the payload.
Track the Codex catalog at .agents/plugins/marketplace.json.
Codex plugin add copies the plugin directory without dereferencing
symlinks, so the nested plugin shipped no skills. Root the Codex
plugin at the repo so the cache copy carries the real skills tree.
Every version declaration now reads 6.11.0-next, matching the
module-manifest.toml files the update check compares against.
… mirror

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.
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.
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 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.
Run the suite with pytest-xdist (-n auto): 17.7s to 5.9s wall measured
locally. Quiet output when green (-q), short tracebacks and a summary
of non-passing outcomes when not (-ra --tb=short). Write nothing to
disk: -p no:cacheprovider stops .pytest_cache and
PYTHONDONTWRITEBYTECODE=1 stops __pycache__; a warm single-directory
bytecode cache measured no faster than cold, so the cache bought
nothing.

Parallelism exposed a race in test_recon_kit.py: two test classes
shared the fixture path tests/_report.md and deleted it after use, so
xdist workers could unlink it under each other. All four file-fixture
tests there now write into per-test temporary directories.
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.
…and toolbox modules

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.
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.
help.md now names https://docs.bmad-method.org/llms.txt as the place to
look when it and the installed skills cannot answer a BMad question; the
index also names the source repository as the final authority. The hub
skill's constraints direct that fetch before conceding a limitation,
restoring the remote-docs grounding the CSV-era bmad-help had via its
_meta row.
parse_packaged_manifest required `knowledge` to equal one hardcoded literal
naming the bmad skill's own help document, so no second module could ship a
manifest that parses. The value is then discarded — it is not a field on
ParsedManifest and nothing reads it — so the check bought nothing.

The cost was disproportionate. discover_installed_copies parses every sibling
manifest before doing any work, so one unacceptable value aborted `bmad update`,
`setup`, and `doctor` for the whole project, including modules whose own
manifests were fine.

This is also what the format spec asks for: the packager rejects unknown or
malformed keys, while `bmad` ignores what it does not recognize, so a manifest
from a newer format cannot break an older hub. stamp_release.py keeps its
equality check — it is the packager, and it only ever runs against this repo's
manifests.

`knowledge` stays required and non-empty. CIS points it at its published
llms.txt, the same remote-docs grounding help.md already names for BMM.
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.
The hub bound every route to references/help.md and was told to treat no
manifest key but `module` as routing, so a module shipped from another
repository was visible and unroutable. Discovery now reads each sound
manifest's free-form `knowledge` text and follows it, for the module the
question concerns, to the document it names.

help.md stops being privileged: it opens with the discovery model and carries
its method/toolbox content as a labelled section — which is simply what those
two modules' manifests point at.
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.
`load_central_config` read `_bmad/config.user.toml` as layer 2 while
setup.py listed the same file in LEGACY_LEFTOVERS — debris from the
classic installer that doctor reports read-only and never writes. The
mechanism that populated it (the removed JS installer's `module.yaml`
`scope:` partition) is gone, so nothing creates the file and the
resolver read a layer that could only ever be hand-written into a
directory documented as installer-owned.

Central config is now three layers, matching load_customization:
config.toml -> custom/config.toml -> custom/config.user.toml.

Tests keep writing config.user.toml and assert a key unique to it never
reaches the merge, pinning the removal.
Sixteen skills read `_bmad/bmm/config.yaml` directly — a flattened
legacy YAML projection that setup.py writes only for `core` and `bmm`.
It coerces every value to a string, lets `[core]` silently win key
collisions with `[modules.bmm]`, and is generated once at setup, so the
`_bmad/custom/` override layers never reach it.

Each now calls resolve_config.py with the exact keys it needs, matching
the eight skills that already did. Nothing reads either YAML projection
after this; setup.py still writes them.

Values stay under `modules.bmm`, which is where config.template.toml
writes them, though these skills declare `module = "method"`.

Drops the fallbacks with them: the `config.user.yaml` hedge (nothing
writes that file), the missing-key defaults, and "never block". The
config step always follows a resolve_customization.py call, so a missing
scripts directory has already surfaced by then.
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
Main renamed bmad-checkpoint-preview to bmad-walkthrough; apply the
same rename under skills/, repoint the hub help doc, and drop the
v6 shim main added since this branch removes the shim tree.
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.
@alexeyv
alexeyv force-pushed the feat/npx-skills-distribution branch from 518c239 to 9f77c1b Compare September 1, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant