Skip to content

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

Merged
alexeyv merged 100 commits into
devfrom
feat/npx-skills-distribution
Sep 5, 2026
Merged

feat!: distribute BMAD as a flat skills tree via npx skills and plugin marketplaces#2768
alexeyv merged 100 commits into
devfrom
feat/npx-skills-distribution

Conversation

@alexeyv

@alexeyv alexeyv commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Why

BMAD's skills depended on its npm installer to turn nested source files into an
installation. This PR makes the repository itself installable: 29 skills live
under skills/<canonical-id>/, and the bmad hub handles project setup,
update checks, and repair.

Users can install with npx skills add bmad-code-org/BMAD-METHOD, or use the
Claude Code and Codex marketplaces supplied by
bmad-code-org/bmad-plugins.

What changes

  • Flat skills distribution. Each skill carries its own instructions and a
    module-manifest.toml declaring its module, version, update source, and
    knowledge location. The tree contains 21 method skills and eight
    toolbox skills, including the bmad hub. No packaging step is needed for
    the Skills CLI.
  • Project runtime. bmad setup creates scripts, configuration, and output
    directories, preserving existing answers and customizations on subsequent
    runs. bmad update checks installed versions without installing updates;
    plugin copies report plugin-managed. bmad doctor repairs an existing
    runtime. Deterministic work runs through Python.
  • Release source. All source manifests and the stamper use
    github:bmad-code-org/BMAD-METHOD/skills. GitHub update checks explicitly
    read main. Plugin packaging lives in the separate plugin repository.
  • Release process. Stamp the release on dev, merge a devmain
    PR with a merge commit, tag that merge commit, then stamp the next patch
    placeholder on dev. There are no release branches or back-merges.
    The stamper synchronizes all manifests, package.json, and both root
    version fields in package-lock.json, preserving dependency versions.
    It rejects invalid SemVer, -dev, and build metadata.
  • CI and contribution targets. Quality runs on pushes to main and
    dev, and on PRs to any branch. A metadata-only pull_request_target
    workflow retargets newly opened PRs from main to dev, except when the
    head branch is dev. Contributor guidance directs 6.12 npm maintenance
    to V6.12.
  • Installer removal and tooling. Remove the classic installer, npm
    publishing pipeline, and legacy shim skills. Skill and file-reference
    validation and renderer snapshot tests use Python. CLAUDE.md imports
    AGENTS.md; the site tooling directory is docs-site/.
  • Documentation. The README describes all three install routes and the
    hub commands. The release runbook replaces the temporary rehearsal process.
    Site documentation intentionally continues to describe npm until the first
    official release under the new distribution scheme.

All version declarations remain 6.12.0-next; this PR does not cut a release.

Validation

  • npm ci && npm run quality passes on committed HEAD in the checkout being
    pushed, including formatting, lint, documentation build and link checks,
    JavaScript tests, Python tests, and skill/reference validation.
  • Python: 500 tests and 32 subtests pass.
  • Regression coverage verifies the pinned main URL, package-version
    synchronization, dependency preservation, and no writes on invalid or
    missing package metadata.
  • Stamping a temporary copy with its current version preserves all 31
    versioned files byte-for-byte.
  • npm ci reports 11 dependency advisories (two moderate, nine high);
    dependency remediation is outside this change.

Rollout outside this PR

  • Create V6.12 from v6.12.0, adapt its npm publish workflow, and move
    maintenance PRs there.
  • Create dev from main and retarget this PR to dev before merging.
    Apply the planned protection to main and retarget other development PRs.
  • Cut the first release using the runbook, update the plugin builder's source
    to BMAD-METHOD, rebuild plugins, and verify all three install routes.
  • Update site documentation when the new scheme has an official release.

Replaces #2736.

@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

@alexeyv
alexeyv force-pushed the feat/npx-skills-distribution branch from 9e77de7 to 63e4d3d Compare August 28, 2026 15:47
@alexeyv
alexeyv force-pushed the feat/npx-skills-distribution branch from 63e4d3d to 518c239 Compare August 30, 2026 22:29
@alexeyv
alexeyv force-pushed the feat/npx-skills-distribution branch 2 times, most recently from 9f77c1b to 987bd13 Compare September 3, 2026 18:34
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.
alexeyv and others added 15 commits September 5, 2026 01:43
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 987bd13 to 0258223 Compare September 5, 2026 07:44
Main has released 6.12.0, so the module manifests and the package
version now carry the next placeholder above it.
Checks that ran when a PR was opened against main are stale once the
retarget workflow moves it to dev. Trigger on the edited event and skip
title and body edits, so a retargeted PR gets fresh checks without the
contributor closing and reopening it.
@alexeyv
alexeyv changed the base branch from main to dev September 5, 2026 11:09
@alexeyv
alexeyv merged commit 255c651 into dev Sep 5, 2026
10 checks passed
@blade035

blade035 commented Sep 5, 2026

Copy link
Copy Markdown

Retested on opencode CLI after the flat skills tree landed — working end-to-end. ✅

Environment: opencode 1.18.27 (CLI, non-interactive opencode run), fresh empty directory, installed with npx skills add bmad-code-org/BMAD-METHOD → all 29 skills under .agents/skills/ with a project-local skills-lock.json (nothing written to ~/.agents). BMAD-METHOD main @ beb368e5 (6.13.0-next, i.e. post-merge main — first release isn't cut yet).

Verification (marker-injection repro from #2692, adapted to the new layout):

  1. Discovery — a session in that directory lists all 29 bmad-* skills alongside the user's global skills.
  2. Deterministic load — planted a RETEST_MARKER_a41c token in bmad-brainstorming/SKILL.md and asked the agent to load the skill and report its test token; it returned the exact token. The JSON event stream shows exactly one skill tool call and zero file reads — content is delivered through opencode's native skill mechanism, not left to the model to hunt down.
  3. Root-cause check (the @ resolution detail from fix(installer): generate working OpenCode command pointers #2692): a command file whose body is the old @skills/bmad-brainstorming syntax still injects nothing by itself — the agent only obtains the content by noticing the name and invoking the skill tool on its own (the non-deterministic/agentic path diagnosed back then). A body of @.agents/skills/bmad-brainstorming/SKILL.md resolves as a readable file reference. So the @-as-namespace limitation in opencode is unchanged — but with the installer gone, nothing generates those pointers anymore, which moots it for BMAD.
Raw evidence
$ opencode --version
1.18.27

$ npx skills add bmad-code-org/BMAD-METHOD
◇ Found 29 skills
● Installing all 29 skills
└ Done!   → .agents/skills/ (29 bmad-* folders), project-local skills-lock.json,
            ~/.agents untouched (verified by mtimes)

[Discovery] "list your available skills, names only" →
bmad, bmad-advanced-elicitation, bmad-agent-analyst, bmad-agent-architect,
bmad-agent-dev, bmad-agent-pm, bmad-agent-ux-designer, bmad-architecture,
bmad-brainstorming, bmad-build, bmad-build-auto, bmad-code-review,
bmad-correct-course, bmad-create-epics-and-stories, bmad-customize,
bmad-deep-recon, bmad-forge-idea, bmad-party-mode, bmad-prd, bmad-prfaq,
bmad-product-brief, bmad-project-context, bmad-qa-generate-e2e-tests,
bmad-retrospective, bmad-review, bmad-spec, bmad-sprint-planning, bmad-ux,
bmad-walkthrough   (+ the user's global skills)

[Native skill load] marker planted in bmad-brainstorming/SKILL.md, then
"load the skill and reply with its test token" →
RETEST_MARKER_a41c
JSON events: 1× tool "skill", 0× read/bash — deterministic delivery

[Old pointer syntax] command body: @skills/bmad-brainstorming →
RETEST_MARKER_a41c, but JSON events: 1× tool "skill"
(the agent discovered and invoked the skill itself; the `@` reference injected nothing)

[Path pointer syntax] command body: @.agents/skills/bmad-brainstorming/SKILL.md →
RETEST_MARKER_a41c, JSON events: 1× tool "read" (resolvable file reference)

Thanks for the rework — the new distribution model is a big improvement for opencode users.

@alexeyv
alexeyv deleted the feat/npx-skills-distribution branch September 5, 2026 17:48
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.

2 participants