unwrapped prose

This commit is contained in:
Brian Madison 2026-05-29 21:31:23 -05:00
parent 6addf6464d
commit 6a62b2e0ba
3 changed files with 45 additions and 125 deletions

View File

@ -1,18 +1,12 @@
# 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 (see `docs/spec.md` in `bmad-marketplace`).
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 (see `docs/spec.md` in `bmad-marketplace`).
## 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.
- **Users** install via this skill — no CLI required. Modules land in
`_bmad/<bmad.code>/` alongside the official modules.
- **BMAD-METHOD** treats community-installed modules as a new `source: 'community'`
row in `manifest.yaml`; re-running `bmad install` preserves them (with the
paired `manifest-generator.js` patch).
- **Authors** publish a single repo with `.claude-plugin/plugin.json` that works in both Claude Code's plugin marketplace and BMAD-METHOD.
- **Users** install via this skill — no CLI required. Modules land in `_bmad/<bmad.code>/` alongside the official modules.
- **BMAD-METHOD** treats community-installed modules as a new `source: 'community'` row in `manifest.yaml`; re-running `bmad install` preserves them (with the paired `manifest-generator.js` patch).
## Verbs
@ -27,45 +21,24 @@ bmad-module list [--json]
## Behavior notes
- **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.
- **Hooks / MCP / LSP / Claude subagents** declared in the module manifest
are _copied_ but NOT auto-activated by this skill. Use Claude Code's
plugin manager to wire them up.
- **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.
- **Hooks / MCP / LSP / Claude subagents** declared in the module manifest are _copied_ but NOT auto-activated by this skill. Use Claude Code's plugin manager to wire them up.
## 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 `_bmad/` without `node_modules` and never
runs `npm install` there:
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 `_bmad/` 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 use a small `node:`-only helper
(`lib/semver-lite.mjs`).
- `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 use a small `node:`-only helper (`lib/semver-lite.mjs`).
They re-use no BMAD-METHOD internal modules — the same code runs during
development inside `bmad-marketplace` and after the skill is PR'd into
BMAD-METHOD core.
They re-use no BMAD-METHOD internal modules — the same code runs during development inside `bmad-marketplace` and after the skill is PR'd into BMAD-METHOD core.
## 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.
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/`.
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/`.

View File

@ -5,27 +5,14 @@ description: Install, update, remove, or list community BMAD modules. Use when t
# bmad-module
Manage community BMAD modules — installable packages of skills, agents, and
supporting assets that ship as standalone GitHub repos. Modules land in
`_bmad/<bmad.code>/` alongside official modules and are tracked in the
existing manifests. The same artifact is also loadable as a Claude Code
plugin via its `.claude-plugin/plugin.json` manifest.
Manage community BMAD modules — installable packages of skills, agents, and supporting assets that ship as standalone GitHub repos. Modules land in `_bmad/<bmad.code>/` alongside official modules and are tracked in the existing manifests. The same artifact is also loadable as a Claude Code plugin via its `.claude-plugin/plugin.json` manifest.
## CRITICAL RULES
- NEVER write directly to files under `_bmad/`. All filesystem changes go
through the Node script at `scripts/bmad-module.mjs` — it handles staging,
atomic swaps, manifest updates, and rollback on failure.
- HALT and report cleanly if `_bmad/` is not present in the current working
directory (exit code 10 from the script).
- DO NOT execute hooks, MCP server commands, or any code shipped inside the
module during install. The install copies files; activation is a separate
step the user opts into via Claude Code's plugin manager.
- If the script exits non-zero, report the exit code and stderr verbatim and
stop. Do NOT retry, do NOT try a different verb. The one exception is exit
code 5 (the skill's own bundled runtime files are missing/corrupt): that's a
fixable setup/packaging problem, not a module rejection — relay the script's
"reinstall the skill" guidance instead of reporting a failed install.
- NEVER write directly to files under `_bmad/`. All filesystem changes go through the Node script at `scripts/bmad-module.mjs` — it handles staging, atomic swaps, manifest updates, and rollback on failure.
- HALT and report cleanly if `_bmad/` is not present in the current working directory (exit code 10 from the script).
- DO NOT execute hooks, MCP server commands, or any code shipped inside the module during install. The install copies files; activation is a separate step the user opts into via Claude Code's plugin manager.
- If the script exits non-zero, report the exit code and stderr verbatim and stop. Do NOT retry, do NOT try a different verb. The one exception is exit code 5 (the skill's own bundled runtime files are missing/corrupt): that's a fixable setup/packaging problem, not a module rejection — relay the script's "reinstall the skill" guidance instead of reporting a failed install.
## EXECUTION
@ -40,35 +27,27 @@ The user's request maps to exactly one of:
| `remove` | "remove module X", "uninstall X", "delete X module" |
| `list` | "list modules", "what modules are installed", "show installed modules" |
If the verb is ambiguous (e.g. the user says "manage modules"), ASK which
verb they want before continuing.
If the verb is ambiguous (e.g. the user says "manage modules"), ASK which verb they want before continuing.
### Step 2 — Parse the args
- **install:** the user supplies `<source>``owner/repo` (GitHub short),
a full git URL (`https://…` or `git@…`), or a local path. Optional flags:
`--ref <branch-tag-or-sha>`, `--channel <stable|next|pinned>`, `--dry-run`.
- **update:** the user supplies `<code>` (the `_bmad/<code>/` folder name)
or asks for "all"; in that case use `--all`. Optional `--ref`.
- **remove:** the user supplies `<code>`. Use `--purge` only if they
explicitly say "also remove customizations" or "purge".
- **install:** the user supplies `<source>``owner/repo` (GitHub short), a full git URL (`https://…` or `git@…`), or a local path. Optional flags: `--ref <branch-tag-or-sha>`, `--channel <stable|next|pinned>`, `--dry-run`.
- **update:** the user supplies `<code>` (the `_bmad/<code>/` folder name) or asks for "all"; in that case use `--all`. Optional `--ref`.
- **remove:** the user supplies `<code>`. Use `--purge` only if they explicitly say "also remove customizations" or "purge".
- **list:** no args. Use `--json` if the user asks for machine-readable.
If anything is missing or ambiguous, ASK before invoking.
### Step 3 — Confirm before destructive verbs
For `install`, `update`, and `remove`, summarize what will happen and confirm
once with the user:
For `install`, `update`, and `remove`, summarize what will happen and confirm once with the user:
> About to **install** `acme/acme-devlog` (will create `_bmad/devlog/`).
> Proceed? [y/N]
For `install` you may run a dry-run first (`--dry-run`) and show the file
plan; that counts as the summary — still confirm before the real run.
For `install` you may run a dry-run first (`--dry-run`) and show the file plan; that counts as the summary — still confirm before the real run.
Skip the confirmation step only if the user has already pre-authorized in
this turn (e.g. "go ahead and install acme-md-lint without asking").
Skip the confirmation step only if the user has already pre-authorized in this turn (e.g. "go ahead and install acme-md-lint without asking").
### Step 4 — Invoke the Node script
@ -78,21 +57,15 @@ Run from the project root (the dir containing `_bmad/`):
node <skill-dir>/scripts/bmad-module.mjs <verb> [args...]
```
`<skill-dir>` is wherever the skill files live in the current install. After
this skill ships into BMAD-METHOD that's `_bmad/core/skills/bmad-module/`;
during development it's this repo's `src/core-skills/bmad-module/`.
`<skill-dir>` is wherever the skill files live in the current install. After this skill ships into BMAD-METHOD that's `_bmad/core/skills/bmad-module/`; during development it's this repo's `src/core-skills/bmad-module/`.
Stream stdout and stderr verbatim. Do NOT silence or rewrite them — the
script's own messages are designed for end-user consumption.
Stream stdout and stderr verbatim. Do NOT silence or rewrite them — the script's own messages are designed for end-user consumption.
### Step 5 — Report
On exit 0: paraphrase the script's final line(s) and note any next-step hint
(e.g. "next: run the `bmad-devlog-setup` skill to finish setup").
On exit 0: paraphrase the script's final line(s) and note any next-step hint (e.g. "next: run the `bmad-devlog-setup` skill to finish setup").
On non-zero exit: print the exit code, the stderr message, and stop. Do not
suggest workarounds beyond what the script's message itself suggests
(e.g. "use `update` instead", "move changes into `_bmad/custom/<code>/`").
On non-zero exit: print the exit code, the stderr message, and stop. Do not suggest workarounds beyond what the script's message itself suggests (e.g. "use `update` instead", "move changes into `_bmad/custom/<code>/`").
## EXIT CODES
@ -114,18 +87,12 @@ suggest workarounds beyond what the script's message itself suggests
## EXAMPLES
User: "Install the devlog module from acme/acme-devlog"
→ Confirm, then run:
`node …/scripts/bmad-module.mjs install acme/acme-devlog`
User: "Install the devlog module from acme/acme-devlog" → Confirm, then run: `node …/scripts/bmad-module.mjs install acme/acme-devlog`
User: "Try installing examples/minimal/acme-md-lint first as a dry-run"
→ Run with `--dry-run`, show the plan, then ask whether to proceed for real.
User: "Try installing examples/minimal/acme-md-lint first as a dry-run" → Run with `--dry-run`, show the plan, then ask whether to proceed for real.
User: "What modules do I have installed?"
→ Run `… list`. No confirmation needed (read-only).
User: "What modules do I have installed?" → Run `… list`. No confirmation needed (read-only).
User: "Update the devlog module to v0.5.0"
→ Confirm, then run `… update devlog --ref v0.5.0`.
User: "Update the devlog module to v0.5.0" → Confirm, then run `… update devlog --ref v0.5.0`.
User: "Remove the mdlint module and wipe its customizations too"
→ Confirm, then run `… remove mdlint --purge`.
User: "Remove the mdlint module and wipe its customizations too" → Confirm, then run `… remove mdlint --purge`.

View File

@ -1,24 +1,18 @@
# `lib/vendor/` — vendored runtime dependencies
This directory holds **self-contained, generated** copies of third-party
libraries the skill needs at runtime. They are committed on purpose.
This directory holds **self-contained, generated** copies of third-party libraries the skill needs at runtime. They are committed on purpose.
## Why vendor at all?
The `bmad-module` skill is **copied into a user's project** at
`_bmad/core/skills/bmad-module/` by `npx bmad-method install`. The installer:
The `bmad-module` skill is **copied into a user's project** at `_bmad/core/skills/bmad-module/` by `npx bmad-method install`. The installer:
- strips `node_modules` while copying (`tools/installer/core/installer.js`),
- ships **no** `package.json` under the skill, and
- never runs `npm install` inside `_bmad/`.
So a bare `import 'yaml'` cannot resolve at runtime — it throws
`ERR_MODULE_NOT_FOUND` before any of the skill's exit codes can fire. Every
other script BMAD installs is zero-third-party-dependency; vendoring keeps this
skill self-sufficient the same way, without setup.
So a bare `import 'yaml'` cannot resolve at runtime — it throws `ERR_MODULE_NOT_FOUND` before any of the skill's exit codes can fire. Every other script BMAD installs is zero-third-party-dependency; vendoring keeps this skill self-sufficient the same way, without setup.
Files here are imported by **relative path** (`./vendor/yaml.mjs`), which
resolves regardless of cwd, install location, or `node_modules` presence.
Files here are imported by **relative path** (`./vendor/yaml.mjs`), which resolves regardless of cwd, install location, or `node_modules` presence.
## What's vendored — and what's NOT
@ -27,39 +21,25 @@ resolves regardless of cwd, install location, or `node_modules` presence.
| `yaml` (parse/stringify `_bmad/_config/manifest.yaml`) | **vendored, real library** | `vendor/yaml.mjs` |
| `semver` (`valid` + `validRange` on `plugin.json`) | **dropped** — hand-rolled, `node:` only | `../semver-lite.mjs` |
`manifest.yaml` is **co-owned** with BMAD core, which reads/writes it with the
same `yaml` package and the same `{indent:2, lineWidth:0}` options
(`tools/installer/core/manifest.js`). Hand-rolling a YAML emitter risks
diverging from that on the user's live install state, so we ship the **real**
library and verify byte-identical output in `build-vendor.mjs`. `semver` is
only input-validation of an author's manifest, so it is safe to hand-roll.
`manifest.yaml` is **co-owned** with BMAD core, which reads/writes it with the same `yaml` package and the same `{indent:2, lineWidth:0}` options (`tools/installer/core/manifest.js`). Hand-rolling a YAML emitter risks diverging from that on the user's live install state, so we ship the **real** library and verify byte-identical output in `build-vendor.mjs`. `semver` is only input-validation of an author's manifest, so it is safe to hand-roll.
## `yaml.mjs`
- **GENERATED — do not edit by hand.** An esbuild single-file bundle of the
`yaml` npm package (eemeli/yaml), tree-shaken to just `parse` + `stringify`.
- **GENERATED — do not edit by hand.** An esbuild single-file bundle of the `yaml` npm package (eemeli/yaml), tree-shaken to just `parse` + `stringify`.
- The exact pinned version and build provenance are in the file's header.
- Upstream license is retained inline (`legalComments: 'inline'`).
### Regenerating
After bumping `yaml` (or esbuild) in the repo's **root** `package.json` +
`npm install`:
After bumping `yaml` (or esbuild) in the repo's **root** `package.json` + `npm install`:
```bash
npm run vendor:build # regenerate this yaml.mjs
npm run vendor:check # verify it's in sync (what CI runs)
```
The build is **deterministic** for a given `yaml` + `esbuild` version (both
pinned in the lockfile) and self-checks a parse→stringify round-trip.
The build is **deterministic** for a given `yaml` + `esbuild` version (both pinned in the lockfile) and self-checks a parse→stringify round-trip.
**You don't have to remember to do this.** `vendor:check` is wired into
`npm test` (husky pre-commit) and `npm run quality` (the `validate` job in
`.github/workflows/quality.yaml`). If the committed bundle drifts from the
installed `yaml`/`esbuild` version, those gates fail with a message telling you
to run `npm run vendor:build` — so a bump can't land with a stale bundle, and
manifest writes stay byte-identical between BMAD core and this skill.
**You don't have to remember to do this.** `vendor:check` is wired into `npm test` (husky pre-commit) and `npm run quality` (the `validate` job in `.github/workflows/quality.yaml`). If the committed bundle drifts from the installed `yaml`/`esbuild` version, those gates fail with a message telling you to run `npm run vendor:build` — so a bump can't land with a stale bundle, and manifest writes stay byte-identical between BMAD core and this skill.
Lint/format intentionally ignore this directory (see `eslint.config.mjs`
global ignores and `.prettierignore`) — it is generated, not authored.
Lint/format intentionally ignore this directory (see `eslint.config.mjs` global ignores and `.prettierignore`) — it is generated, not authored.