unwrapped prose
This commit is contained in:
parent
6addf6464d
commit
6a62b2e0ba
|
|
@ -1,18 +1,12 @@
|
||||||
# bmad-module
|
# bmad-module
|
||||||
|
|
||||||
The core BMAD skill for installing, updating, removing, and listing community
|
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`).
|
||||||
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
|
## How it fits
|
||||||
|
|
||||||
- **Authors** publish a single repo with `.claude-plugin/plugin.json` that
|
- **Authors** publish a single repo with `.claude-plugin/plugin.json` that works in both Claude Code's plugin marketplace and BMAD-METHOD.
|
||||||
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.
|
||||||
- **Users** install via this skill — no CLI required. Modules land in
|
- **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).
|
||||||
`_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
|
## Verbs
|
||||||
|
|
||||||
|
|
@ -27,45 +21,24 @@ bmad-module list [--json]
|
||||||
|
|
||||||
## Behavior notes
|
## Behavior notes
|
||||||
|
|
||||||
- **Source of truth** for what was installed is `_bmad/_config/files-manifest.csv`
|
- **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.
|
||||||
(per-file hashes) and `_bmad/_config/skill-manifest.csv` (one row per
|
- **`update`** refuses to overwrite locally-modified files (hash mismatch against the recorded hash). Move overrides into `_bmad/custom/<code>/` and retry.
|
||||||
shipped skill). `manifest.yaml` carries the source/version/sha tuple.
|
- **`remove`** without `--purge` preserves `_bmad/custom/<code>/` so a re-install picks the customizations back up. `--purge` deletes them.
|
||||||
- **`update`** refuses to overwrite locally-modified files (hash mismatch
|
- **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.
|
||||||
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
|
## Implementation
|
||||||
|
|
||||||
The skill itself is a thin verb router (`SKILL.md`). `scripts/bmad-module.mjs`
|
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:
|
||||||
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`
|
- `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.
|
||||||
library** (`lib/vendor/yaml.mjs`, regenerated by `lib/vendor/build-vendor.mjs`)
|
- `semver` validity/range checks use a small `node:`-only helper (`lib/semver-lite.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
|
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.
|
||||||
development inside `bmad-marketplace` and after the skill is PR'd into
|
|
||||||
BMAD-METHOD core.
|
|
||||||
|
|
||||||
## Exit codes
|
## Exit codes
|
||||||
|
|
||||||
See `SKILL.md` for the full table. The script's stderr always names the
|
See `SKILL.md` for the full table. The script's stderr always names the condition; the codes are stable so tooling can branch.
|
||||||
condition; the codes are stable so tooling can branch.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
Integration tests live in `tests/integration.test.sh` and run end-to-end on
|
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/`.
|
||||||
a fresh BMAD install. Fixtures for negative cases (collisions, path
|
|
||||||
traversal, reserved codes) are under `tests/fixtures/`.
|
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,14 @@ description: Install, update, remove, or list community BMAD modules. Use when t
|
||||||
|
|
||||||
# bmad-module
|
# bmad-module
|
||||||
|
|
||||||
Manage community BMAD modules — installable packages of skills, agents, and
|
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.
|
||||||
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
|
## CRITICAL RULES
|
||||||
|
|
||||||
- NEVER write directly to files under `_bmad/`. All filesystem changes go
|
- 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.
|
||||||
through the Node script at `scripts/bmad-module.mjs` — it handles staging,
|
- HALT and report cleanly if `_bmad/` is not present in the current working directory (exit code 10 from the script).
|
||||||
atomic swaps, manifest updates, and rollback on failure.
|
- 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.
|
||||||
- HALT and report cleanly if `_bmad/` is not present in the current working
|
- 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.
|
||||||
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
|
## EXECUTION
|
||||||
|
|
||||||
|
|
@ -40,35 +27,27 @@ The user's request maps to exactly one of:
|
||||||
| `remove` | "remove module X", "uninstall X", "delete X module" |
|
| `remove` | "remove module X", "uninstall X", "delete X module" |
|
||||||
| `list` | "list modules", "what modules are installed", "show installed modules" |
|
| `list` | "list modules", "what modules are installed", "show installed modules" |
|
||||||
|
|
||||||
If the verb is ambiguous (e.g. the user says "manage modules"), ASK which
|
If the verb is ambiguous (e.g. the user says "manage modules"), ASK which verb they want before continuing.
|
||||||
verb they want before continuing.
|
|
||||||
|
|
||||||
### Step 2 — Parse the args
|
### Step 2 — Parse the args
|
||||||
|
|
||||||
- **install:** the user supplies `<source>` — `owner/repo` (GitHub short),
|
- **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`.
|
||||||
a full git URL (`https://…` or `git@…`), or a local path. Optional flags:
|
- **update:** the user supplies `<code>` (the `_bmad/<code>/` folder name) or asks for "all"; in that case use `--all`. Optional `--ref`.
|
||||||
`--ref <branch-tag-or-sha>`, `--channel <stable|next|pinned>`, `--dry-run`.
|
- **remove:** the user supplies `<code>`. Use `--purge` only if they explicitly say "also remove customizations" or "purge".
|
||||||
- **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.
|
- **list:** no args. Use `--json` if the user asks for machine-readable.
|
||||||
|
|
||||||
If anything is missing or ambiguous, ASK before invoking.
|
If anything is missing or ambiguous, ASK before invoking.
|
||||||
|
|
||||||
### Step 3 — Confirm before destructive verbs
|
### Step 3 — Confirm before destructive verbs
|
||||||
|
|
||||||
For `install`, `update`, and `remove`, summarize what will happen and confirm
|
For `install`, `update`, and `remove`, summarize what will happen and confirm once with the user:
|
||||||
once with the user:
|
|
||||||
|
|
||||||
> About to **install** `acme/acme-devlog` (will create `_bmad/devlog/`).
|
> About to **install** `acme/acme-devlog` (will create `_bmad/devlog/`).
|
||||||
> Proceed? [y/N]
|
> Proceed? [y/N]
|
||||||
|
|
||||||
For `install` you may run a dry-run first (`--dry-run`) and show the file
|
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.
|
||||||
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
|
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").
|
||||||
this turn (e.g. "go ahead and install acme-md-lint without asking").
|
|
||||||
|
|
||||||
### Step 4 — Invoke the Node script
|
### 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...]
|
node <skill-dir>/scripts/bmad-module.mjs <verb> [args...]
|
||||||
```
|
```
|
||||||
|
|
||||||
`<skill-dir>` is wherever the skill files live in the current install. After
|
`<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/`.
|
||||||
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
|
Stream stdout and stderr verbatim. Do NOT silence or rewrite them — the script's own messages are designed for end-user consumption.
|
||||||
script's own messages are designed for end-user consumption.
|
|
||||||
|
|
||||||
### Step 5 — Report
|
### Step 5 — Report
|
||||||
|
|
||||||
On exit 0: paraphrase the script's final line(s) and note any next-step hint
|
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").
|
||||||
(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
|
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>/`").
|
||||||
suggest workarounds beyond what the script's message itself suggests
|
|
||||||
(e.g. "use `update` instead", "move changes into `_bmad/custom/<code>/`").
|
|
||||||
|
|
||||||
## EXIT CODES
|
## EXIT CODES
|
||||||
|
|
||||||
|
|
@ -114,18 +87,12 @@ suggest workarounds beyond what the script's message itself suggests
|
||||||
|
|
||||||
## EXAMPLES
|
## EXAMPLES
|
||||||
|
|
||||||
User: "Install the devlog module from acme/acme-devlog"
|
User: "Install the devlog module from acme/acme-devlog" → Confirm, then run: `node …/scripts/bmad-module.mjs install 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"
|
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.
|
||||||
→ Run with `--dry-run`, show the plan, then ask whether to proceed for real.
|
|
||||||
|
|
||||||
User: "What modules do I have installed?"
|
User: "What modules do I have installed?" → Run `… list`. No confirmation needed (read-only).
|
||||||
→ Run `… list`. No confirmation needed (read-only).
|
|
||||||
|
|
||||||
User: "Update the devlog module to v0.5.0"
|
User: "Update the devlog module to v0.5.0" → Confirm, then run `… update devlog --ref v0.5.0`.
|
||||||
→ Confirm, then run `… update devlog --ref v0.5.0`.
|
|
||||||
|
|
||||||
User: "Remove the mdlint module and wipe its customizations too"
|
User: "Remove the mdlint module and wipe its customizations too" → Confirm, then run `… remove mdlint --purge`.
|
||||||
→ Confirm, then run `… remove mdlint --purge`.
|
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
# `lib/vendor/` — vendored runtime dependencies
|
# `lib/vendor/` — vendored runtime dependencies
|
||||||
|
|
||||||
This directory holds **self-contained, generated** copies of third-party
|
This directory holds **self-contained, generated** copies of third-party libraries the skill needs at runtime. They are committed on purpose.
|
||||||
libraries the skill needs at runtime. They are committed on purpose.
|
|
||||||
|
|
||||||
## Why vendor at all?
|
## Why vendor at all?
|
||||||
|
|
||||||
The `bmad-module` skill is **copied into a user's project** at
|
The `bmad-module` skill is **copied into a user's project** at `_bmad/core/skills/bmad-module/` by `npx bmad-method install`. The installer:
|
||||||
`_bmad/core/skills/bmad-module/` by `npx bmad-method install`. The installer:
|
|
||||||
|
|
||||||
- strips `node_modules` while copying (`tools/installer/core/installer.js`),
|
- strips `node_modules` while copying (`tools/installer/core/installer.js`),
|
||||||
- ships **no** `package.json` under the skill, and
|
- ships **no** `package.json` under the skill, and
|
||||||
- never runs `npm install` inside `_bmad/`.
|
- never runs `npm install` inside `_bmad/`.
|
||||||
|
|
||||||
So a bare `import 'yaml'` cannot resolve at runtime — it throws
|
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.
|
||||||
`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
|
Files here are imported by **relative path** (`./vendor/yaml.mjs`), which resolves regardless of cwd, install location, or `node_modules` presence.
|
||||||
resolves regardless of cwd, install location, or `node_modules` presence.
|
|
||||||
|
|
||||||
## What's vendored — and what's NOT
|
## 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` |
|
| `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` |
|
| `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
|
`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.
|
||||||
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`
|
## `yaml.mjs`
|
||||||
|
|
||||||
- **GENERATED — do not edit by hand.** An esbuild single-file bundle of the
|
- **GENERATED — do not edit by hand.** An esbuild single-file bundle of the `yaml` npm package (eemeli/yaml), tree-shaken to just `parse` + `stringify`.
|
||||||
`yaml` npm package (eemeli/yaml), tree-shaken to just `parse` + `stringify`.
|
|
||||||
- The exact pinned version and build provenance are in the file's header.
|
- The exact pinned version and build provenance are in the file's header.
|
||||||
- Upstream license is retained inline (`legalComments: 'inline'`).
|
- Upstream license is retained inline (`legalComments: 'inline'`).
|
||||||
|
|
||||||
### Regenerating
|
### Regenerating
|
||||||
|
|
||||||
After bumping `yaml` (or esbuild) in the repo's **root** `package.json` +
|
After bumping `yaml` (or esbuild) in the repo's **root** `package.json` + `npm install`:
|
||||||
`npm install`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run vendor:build # regenerate this yaml.mjs
|
npm run vendor:build # regenerate this yaml.mjs
|
||||||
npm run vendor:check # verify it's in sync (what CI runs)
|
npm run vendor:check # verify it's in sync (what CI runs)
|
||||||
```
|
```
|
||||||
|
|
||||||
The build is **deterministic** for a given `yaml` + `esbuild` version (both
|
The build is **deterministic** for a given `yaml` + `esbuild` version (both pinned in the lockfile) and self-checks a parse→stringify round-trip.
|
||||||
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
|
**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.
|
||||||
`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`
|
Lint/format intentionally ignore this directory (see `eslint.config.mjs` global ignores and `.prettierignore`) — it is generated, not authored.
|
||||||
global ignores and `.prettierignore`) — it is generated, not authored.
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue