Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
a0a573b
feat/bmad-module. small patch to prevent community modules from being…
pbean May 22, 2026
e96f16b
feat/bmad-module: community module manager skill created
pbean May 22, 2026
a0fba4b
fix(bmad-module): make skill self-contained so it works after npx ins…
pbean May 24, 2026
1fb1cf5
bug: fixed atomicwrites
pbean May 24, 2026
6d49cc5
feat(bmad-module): manifest-driven copy plan with canonical path rewr…
pbean May 29, 2026
354b252
feat(bmad-module): register skill in core module-help.csv
pbean May 29, 2026
dfae029
fix(bmad-module): copy file-typed agents/commands entries on install
pbean May 29, 2026
bd63b83
test(bmad-module): make integration suite self-contained
pbean May 29, 2026
ce461c0
style(bmad-module): apply prettier formatting to README and SKILL
pbean May 29, 2026
6addf64
Merge remote-tracking branch 'origin/main' into feat/bmad-marketplace…
pbean May 29, 2026
6a62b2e
unwrapped prose
bmadcode May 30, 2026
af52c7b
feat(bmad-module): distribute installed skills to the user's chosen IDEs
pbean May 30, 2026
546826b
feat(bmad-module): complete custom-module install to match the instal…
pbean Jun 1, 2026
8d628b5
fix(validate-skills): skip tests/fixtures when discovering skills
pbean Jun 1, 2026
9bc76ac
feat(installer): recognize new module system (plugin.json#bmad)
pbean Jun 1, 2026
4d036bc
docs(bmad-module): correct script location to installed IDE skills dir
pbean Jun 2, 2026
bca9388
docs(bmad-module): drop stale temp-repo references and install paths
pbean Jun 2, 2026
acbae8e
fix(installer): resolve legacy module.yaml above skills' common parent
pbean Jun 3, 2026
be1235f
feat(installer): prompt to pick module.yaml when a plugin has several
pbean Jun 3, 2026
08f25d0
refactor(bmad-module): tighten SKILL.md and drop dead exit code 40
pbean Jun 3, 2026
34b2fb1
feat(bmad-module): install legacy marketplace.json modules in the skill
pbean Jun 6, 2026
b41c3b0
feat(bmad-module): reach installer parity on source parsing, channels…
pbean Jun 6, 2026
1e4033f
Merge remote-tracking branch 'origin/main' into feat/bmad-marketplace…
pbean Jun 9, 2026
ddac866
Merge remote-tracking branch 'origin/main' into feat/bmad-marketplace…
pbean Jun 19, 2026
24d4f64
test(bmad-module): track fixture .mcp.json so CI install test passes
pbean Jun 20, 2026
02806ba
fix(bmad-module): harden source/remove/update paths and channel input
pbean Jun 20, 2026
08c7379
fix(bmad-module): prevent atomicSwapDir data loss + harden ide-sync/w…
pbean Jun 20, 2026
c845e78
fix(bmad-module): correctness — channel/.git, config-gen, install-pla…
pbean Jun 20, 2026
31d1152
fix(bmad-module): surface malformed plugin.json + fail CI custom-sour…
pbean Jun 20, 2026
e76b268
ci(bmad-module): run test:ide-sync in the validate job
pbean Jun 20, 2026
cbf87c4
docs(bmad-module): add CLI reference page
pbean Jun 20, 2026
08dc71f
chore(bmad-module): frontmatter/CRLF, test hygiene, fixture corrections
pbean Jun 20, 2026
53b21a7
Merge branch 'main' into feat/bmad-marketplace-plugin
bmadcode Jun 20, 2026
ce314ba
fix(bmad-module): address review — backup safety, legacy update, help…
pbean Jun 21, 2026
962e9a0
chore(bmad-module): diagnose vendor:check ide-sync drift in CI
pbean Jun 21, 2026
f8305b5
Merge remote-tracking branch 'origin/main' into feat/bmad-marketplace…
pbean Jun 21, 2026
244deb8
chore(bmad-module): regenerate vendored platform-codes after main sync
pbean Jun 21, 2026
1715dfb
Merge branch 'main' into feat/bmad-marketplace-plugin
pbean Jun 21, 2026
503a9f8
chore(bmad-module): regenerate vendored bundles for esbuild 0.28.1
pbean Jun 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,21 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Vendor bundle freshness (bmad-module yaml)
run: npm run vendor:check

- name: Test agent compilation components
run: npm run test:install

- name: Test IDE sync (engine/bundle parity)
run: npm run test:ide-sync

- name: Test bmad-module skill (source parsing + channels)
run: npm run test:skill-source

- name: Test bmad-module skill (end-to-end install/update/remove)
run: npm run test:skill

- name: Validate file references
run: npm run validate:refs

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ CLAUDE.local.md
.analysis/


# Test fixtures need a committed .mcp.json (the rule above ignores real projects')
!src/core-skills/bmad-module/tests/fixtures/**/.mcp.json

z*/
!docs/zh-cn/

Expand Down
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ _bmad*/
# IDE integration folders (user-specific, not in repo)
.junie/

# Generated vendored bundles in the bmad-module skill (not authored source)
src/core-skills/bmad-module/scripts/lib/vendor/**

# Quality scan artifacts produced by bmad-workflow-builder
# (per-skill .analysis/ folders contain JSON/HTML reports that should
# not block commits with formatting checks)
Expand Down
123 changes: 123 additions & 0 deletions docs/reference/bmad-module-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
title: bmad-module CLI
description: Reference for the bmad-module command — install, update, remove, and list BMad community/custom modules from inside a project.
sidebar:
order: 7
---

`bmad-module` installs, updates, removes, and lists **community and custom BMad modules** in a project that already has BMad installed (a `_bmad/` directory). It is the command behind the `bmad-module` skill, and it mirrors the official installer's behavior for custom modules so a skill-driven install lands the same on-disk state.

Run it from your project root (the directory containing `_bmad/`), or point it elsewhere with `--project-dir`.

## Commands

```
bmad-module install <source> [--ref <ref>] [--channel <c>] [--module <code>] [--set <code>.<key>=<v>] [--dry-run]
bmad-module update <code|--all> [--ref <ref>] [--channel <c>] [--set <code>.<key>=<v>]
bmad-module remove <code> [--purge]
bmad-module list [--json]
```

### install `<source>`

Resolve a module from `<source>`, copy it into `_bmad/<code>/`, generate its config and agent roster, create its declared working directories, and distribute its skills to the coding assistants (IDEs) you chose at `bmad install` time.

`<source>` may be:

- A GitHub shorthand — `acme/acme-devlog`
- A full Git URL — `https://github.com/acme/acme-devlog` (optionally with `@ref` or `/tree/<ref>`)
- A local path — `./examples/minimal/acme-md-lint`
- A legacy `marketplace.json` repo — `bmad-code-org/bmad-module-game-dev-studio`

| Flag | Description |
| --- | --- |
| `--ref <ref>` | Clone a specific git tag/branch/commit. Implies `--channel pinned`. |
| `--channel <c>` | Release channel: `stable`, `next`, or `pinned` (see [Channels](#channels)). |
| `--module <code>` | Pick one module by `code` when a legacy `marketplace.json` repo resolves to more than one. |
| `--set <code>.<key>=<v>` | Override a module config answer. Repeatable. |
| `--dry-run` | Print the resolved install plan without writing anything. |

### update `<code>` | `--all`

Re-resolve an installed module and atomically swap in the new version. Aborts (exit `80`) if you have locally modified tracked files, so your edits are never silently overwritten.

| Flag | Description |
| --- | --- |
| `--all` | Update every installed community/custom module instead of a single `<code>`. |
| `--ref <ref>` | Update to a specific git ref. |
| `--channel <c>` | Switch/track a release channel (`stable`, `next`, `pinned`). |
| `--set <code>.<key>=<v>` | Override a module config answer. Repeatable. |

### remove `<code>`

Remove an installed module: delete `_bmad/<code>/`, prune its entries from the central config and help catalog, and remove its skills from your IDE directories.

| Flag | Description |
| --- | --- |
| `--purge` | Also delete the module's user customizations under `_bmad/custom/` (e.g. `_bmad/custom/<skill>.toml`). Without `--purge`, customizations are left intact. |

### list

List installed community/custom modules with their code, version, channel, and source.

| Flag | Description |
| --- | --- |
| `--json` | Emit machine-readable JSON instead of a formatted table. |

## Global flags

| Flag | Description |
| --- | --- |
| `--project-dir <path>` | Project root containing `_bmad/` (default: current directory). |

## Channels

`bmad-module` mirrors the official installer's channel semantics:

- **`stable`** — the latest non-prerelease GitHub release tag. Falls back to `next` (with a note) when the repo has no tags, isn't a GitHub repo, or the tags API is unreachable.
- **`next`** — the repository's default branch (`main`).
- **`pinned`** — the exact `--ref` you supply (or an `@ref` / `/tree/<ref>` parsed from the source). `--channel pinned` without a `--ref` falls back to `next`.

An explicit `--ref` (or an `@ref` in the source) implies `pinned`. An unknown `--channel` value is rejected with a usage error rather than silently tracking `next`.

## Examples

```bash
# Install from GitHub shorthand
bmad-module install acme/acme-devlog

# Install a local module
bmad-module install ./examples/minimal/acme-md-lint

# Pin to a release tag
bmad-module install https://github.com/acme/acme-devlog --ref v0.4.0

# Install a legacy marketplace.json module
bmad-module install bmad-code-org/bmad-module-game-dev-studio

# Override a config answer at install time
bmad-module install acme/acme-devlog --set devlog.author="Ada Lovelace"

# List, update, and remove
bmad-module list
bmad-module update devlog
bmad-module update --all
bmad-module remove mdlint --purge
```

## Exit codes

| Code | Meaning |
| --- | --- |
| `0` | Success |
| `2` | Usage error (bad arguments, unknown flag/channel) |
| `5` | Skill runtime files missing/corrupt — reinstall the skill |
| `10` | No `_bmad/` directory in the project |
| `20` | Missing or invalid `plugin.json` |
| `21` | Reserved `bmad.code` |
| `30` | Prefix collision with an existing module |
| `50` | Filesystem commit failed |
| `60` | Network / git clone failed |
| `70` | Path traversal detected in a manifest path |
| `80` | Update aborted: locally modified files |
| `90` | No such installed module |
26 changes: 26 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ export default [
'dist/**',
'coverage/**',
'**/*.min.js',
// Generated, self-contained vendored bundles shipped with the bmad-module
// skill (regenerated by its build-vendor.mjs) — not authored source.
'src/core-skills/bmad-module/scripts/lib/vendor/**',
'test/template-test-generator/**',
'test/fixtures/**',
'_bmad*/**',
Expand Down Expand Up @@ -117,6 +120,29 @@ export default [
},
},

// bmad-module core skill: self-contained ESM CLI support scripts.
// Same internal-script relaxations as tools/** and src/scripts/** above,
// plus a few cosmetic rules. The code is reviewed and integration-tested
// as-is (the exit-code contract relies on process.exit).
{
files: ['src/core-skills/bmad-module/scripts/**/*.mjs', 'src/core-skills/bmad-module/scripts/**/*.js'],
rules: {
'n/hashbang': 'off',
'n/no-process-exit': 'off',
'unicorn/no-process-exit': 'off',
'unicorn/prefer-top-level-await': 'off',
'no-unused-vars': 'off',
'unicorn/no-array-reduce': 'off',
'unicorn/no-array-callback-reference': 'off',
'unicorn/no-array-for-each': 'off',
'unicorn/catch-error-name': 'off',
'unicorn/switch-case-braces': 'off',
'unicorn/explicit-length-check': 'off',
'unicorn/prefer-string-replace-all': 'off',
'unicorn/prefer-string-raw': 'off',
},
},

// ESLint config file should not be checked for publish-related Node rules
{
files: ['eslint.config.mjs'],
Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,20 @@
"lint:fix": "eslint . --ext .js,.cjs,.mjs,.yaml --fix",
"lint:md": "markdownlint-cli2 \"**/*.md\"",
"prepare": "command -v husky >/dev/null 2>&1 && husky || exit 0",
"quality": "npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:urls && npm run validate:refs && npm run validate:skills && npm run docs:validate-sidebar",
"quality": "npm run vendor:check && npm run format:check && npm run lint && npm run lint:md && npm run docs:build && npm run test:install && npm run test:ide-sync && npm run test:urls && npm run test:skill-source && npm run test:skill && npm run validate:refs && npm run validate:skills && npm run docs:validate-sidebar",
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:channels && npm run lint && npm run lint:md && npm run format:check",
"test": "npm run vendor:check && npm run test:refs && npm run test:install && npm run test:ide-sync && npm run test:urls && npm run test:channels && npm run test:skill-source && npm run test:skill && npm run lint && npm run lint:md && npm run format:check",
"test:channels": "node test/test-installer-channels.js",
"test:ide-sync": "node test/test-ide-sync.js",
"test:install": "node test/test-installation-components.js",
"test:refs": "node test/test-file-refs-csv.js",
"test:skill": "bash src/core-skills/bmad-module/tests/integration.test.sh",
"test:skill-source": "node test/test-bmad-module-source.mjs",
"test:urls": "node test/test-parse-source-urls.js",
"validate:refs": "node tools/validate-file-refs.js --strict",
"validate:skills": "node tools/validate-skills.js --strict"
"validate:skills": "node tools/validate-skills.js --strict",
"vendor:build": "node src/core-skills/bmad-module/scripts/lib/vendor/build-vendor.mjs && node src/core-skills/bmad-module/scripts/lib/vendor/build-ide-sync.mjs",
"vendor:check": "node src/core-skills/bmad-module/scripts/lib/vendor/build-vendor.mjs --check && node src/core-skills/bmad-module/scripts/lib/vendor/build-ide-sync.mjs --check"
},
"lint-staged": {
"*.{js,cjs,mjs}": [
Expand Down
50 changes: 50 additions & 0 deletions src/core-skills/bmad-module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 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.json` that works in both Claude Code's plugin marketplace and BMAD-METHOD.
- **Legacy modules** (a `.claude-plugin/marketplace.json` + `module.yaml`, the pre-`plugin.json` format) also install: `install` resolves a legacy repo into a synthetic manifest and runs it through the same pipeline. See `lib/legacy-resolver.mjs`, a self-contained port of the full installer's `PluginResolver` strategies.
- **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 at `bmad install` time (the `ides:` list in `_bmad/_config/manifest.yaml`), exactly like official modules.
- **BMAD-METHOD** treats community-installed modules as a new `source: 'community'` row in `manifest.yaml`; re-running `bmad install` preserves them (`manifest-generator.js` carries `source: '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.json` metadata), 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. See `lib/cache.mjs`.
- **Channels.** `lib/channel-resolver.mjs` (a `node:`-only port of the installer's `channel-resolver.js`) resolves `--channel`: `pinned` → an explicit `--ref`/`@ref`; `stable` → the latest non-prerelease GitHub release tag (falls back to `next` when 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. `update` re-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.yaml` carries the source/version/sha tuple.
- **`update`** refuses to overwrite locally-modified files (hash mismatch against the recorded hash). Move overrides into `_bmad/custom/<code>/` and retry.
- **`remove`** without `--purge` preserves `_bmad/custom/<code>/` so a re-install picks the customizations back up. `--purge` deletes 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 from `tools/installer/ide/*` by `lib/vendor/build-ide-sync.mjs`, gated by `vendor:check`). The skill execs it locally — no npx, no network. The same engine also backs the `bmad ide-sync` CLI 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 at `bmad 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`: if `marketplace.json` is present, the skill resolves the module via `module.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-spec `plugin.json` authors 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.yaml` is read/written with a **vendored copy of the real `yaml` library** (`lib/vendor/yaml.mjs`, regenerated by `lib/vendor/build-vendor.mjs`) so it stays byte-identical to BMAD core's writer.
- `semver` validity/range checks **and** the version comparison the stable-channel resolver needs (`prerelease`, `compare`, `rcompare`) use a small `node:`-only helper (`lib/semver-lite.mjs`) instead of the `semver` package.
- The shared clone cache (`lib/cache.mjs`) and channel resolution (`lib/channel-resolver.mjs`) use only `node:child_process` / `node:https` so 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`.
Loading
Loading