- frontmatter: accept a block that ends at EOF (optional trailing newline). - legacy-resolver: accept CRLF frontmatter delimiters. - integration.test.sh: unique mktemp stderr capture, explicit if/then/else assertions (drop the SC2015 && ok || ko chains), plus unknown-flag and invalid-channel usage-error cases. - test-installation-components: clear the resolution cache in a finally. - acme-devlog fixtures: correct the uninstall note to the flat TOML layout, replace `ls -t | head` with a glob/-nt lookup, drop the always-on devlog config file: fact, "run" -> "invoke" wording, and reconcile the devlog-write template contract with the bundled template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| scripts | ||
| tests | ||
| README.md | ||
| SKILL.md | ||
README.md
bmad-module
The core BMAD skill for installing, updating, removing, and listing community BMAD modules. Modules are standalone GitHub repos that conform to the BMAD Module Manifest Spec.
How it fits
- Authors publish a single repo with
.claude-plugin/plugin.jsonthat works in both Claude Code's plugin marketplace and BMAD-METHOD. - Legacy modules (a
.claude-plugin/marketplace.json+module.yaml, the pre-plugin.jsonformat) also install:installresolves a legacy repo into a synthetic manifest and runs it through the same pipeline. Seelib/legacy-resolver.mjs, a self-contained port of the full installer'sPluginResolverstrategies. - Users install via this skill — no CLI required. Modules are staged under
_bmad/<bmad.code>/, then their skills are distributed to the coding assistants the user chose atbmad installtime (theides:list in_bmad/_config/manifest.yaml), exactly like official modules. - BMAD-METHOD treats community-installed modules as a new
source: 'community'row inmanifest.yaml; re-runningbmad installpreserves them (manifest-generator.jscarriessource: 'community'rows through regeneration).
Verbs
bmad-module install <source> [--ref <r>] [--channel <c>] [--module <code>] [--dry-run]
bmad-module update <code|--all> [--ref <r>] [--channel <c>]
bmad-module remove <code> [--purge]
bmad-module list [--json]
<source> accepts owner/repo, a full git URL (https://…, git@…, ssh://, git://), or a local path. A git source may carry an @<tag-or-branch> suffix and may be a browser-style deep link (/tree|blob/<ref>[/<subdir>], GitLab /-/tree/…, Gitea /src/branch/…, or ?path=); parseSource in lib/source.mjs extracts the embedded ref and a repo subdirectory, mirroring the installer's custom-module-manager.js.
Behavior notes
- Source resolution & caching. Git sources are cloned into a shared cache at
~/.bmad/cache/custom-modules/<host>/<owner>/<repo>/(with.bmad-source.json/.bmad-channel.jsonmetadata), the same cache the full installer uses; a matching ref is reused, otherwise the clone is fetched/refreshed, and a fetch failure keeps the stale copy so installs work offline. The install then copies the module root (the subdir, if the URL named one) out of the cache into a throwaway temp tree to stage from — the cache is never mutated. Local sources are copied straight to the temp tree. Seelib/cache.mjs. - Channels.
lib/channel-resolver.mjs(anode:-only port of the installer'schannel-resolver.js) resolves--channel:pinned→ an explicit--ref/@ref;stable→ the latest non-prerelease GitHub release tag (falls back tonextwhen there are no tags, the URL isn't GitHub, or the tags API is unreachable);next(the default for a bare git source) → the default branch.updatere-resolves the channel the module was installed with. - Source of truth for what was installed is
_bmad/_config/files-manifest.csv(per-file hashes) and_bmad/_config/skill-manifest.csv(one row per shipped skill).manifest.yamlcarries the source/version/sha tuple. updaterefuses to overwrite locally-modified files (hash mismatch against the recorded hash). Move overrides into_bmad/custom/<code>/and retry.removewithout--purgepreserves_bmad/custom/<code>/so a re-install picks the customizations back up.--purgedeletes them. Remove also prunes the module's skills from every configured IDE.- IDE distribution runs after every install/update/remove via a self-contained bundle of BMAD's real IDE engine, shipped at
lib/vendor/ide-sync.mjs(built fromtools/installer/ide/*bylib/vendor/build-ide-sync.mjs, gated byvendor:check). The skill execs it locally — no npx, no network. The same engine also backs thebmad ide-syncCLI command and the full installer's IDE setup, so all three stay in lockstep. If the bundle is unreachable on an older install, the skill says so and points the user atbmad ide-sync. - Hooks / MCP / LSP / Claude subagents declared in the module manifest are copied but NOT auto-activated by this skill (they are Claude Code plugin surfaces, not skills). Use Claude Code's plugin manager to wire them up.
- Legacy resolution keys off the absence of a
plugin.json#bmad: ifmarketplace.jsonis present, the skill resolves the module viamodule.yaml(or synthesizes one from SKILL.md frontmatter when none exists). A repo defining more than one module exits 20 with the available codes; re-run with--module <code>. The reserved-code guard (exit 21) is relaxed on the legacy path so first-party modules (gds,bmm, …) install; current-specplugin.jsonauthors still get exit 21.
Implementation
The skill itself is a thin verb router (SKILL.md). scripts/bmad-module.mjs is a zero-import launcher that guards the import graph (a missing/corrupt runtime file becomes a documented exit code, not a raw stack trace); the verb dispatcher lives in scripts/cli.mjs and all filesystem work happens in the lib/ modules. These carry no registry dependencies — important because the installer copies the skill into the IDE skills directories (e.g. .claude/skills/bmad-module/) without node_modules and never runs npm install there:
manifest.yamlis read/written with a vendored copy of the realyamllibrary (lib/vendor/yaml.mjs, regenerated bylib/vendor/build-vendor.mjs) so it stays byte-identical to BMAD core's writer.semvervalidity/range checks and the version comparison the stable-channel resolver needs (prerelease,compare,rcompare) use a smallnode:-only helper (lib/semver-lite.mjs) instead of thesemverpackage.- The shared clone cache (
lib/cache.mjs) and channel resolution (lib/channel-resolver.mjs) use onlynode:child_process/node:httpsso the skill needs no dependencies after distribution.
Exit codes
See SKILL.md for the full table. The script's stderr always names the condition; the codes are stable so tooling can branch.
Tests
Integration tests live in tests/integration.test.sh and run end-to-end on a fresh BMAD install. Fixtures for negative cases (collisions, path traversal, reserved codes) are under tests/fixtures/; legacy-format fixtures (strategy-1 module files, a reserved code, and the synthesize fallback) are under tests/fixtures/examples/legacy/.
The pure (no-network) install plumbing — parseSource URL/@ref/subdir parsing, semver-lite, and the channel-resolver helpers — is unit-tested in test/test-bmad-module-source.mjs (run via npm run test:skill-source, included in npm test), kept at parity with the installer's test/test-parse-source-urls.js and test/test-installer-channels.js.