bmad-method 6.11.1-next.2 → 6.11.1-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/bmm-skills/agents/bmad-agent-analyst/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-architect/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-dev/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-pm/customize.toml +1 -3
- package/src/bmm-skills/agents/bmad-agent-ux-designer/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-architecture/SKILL.md +1 -1
- package/src/bmm-skills/plan/bmad-architecture/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-create-epics-and-stories/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-generate-project-context/SKILL.md +2 -0
- package/src/bmm-skills/plan/bmad-prd/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-prfaq/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-product-brief/customize.toml +5 -6
- package/src/bmm-skills/plan/bmad-project-context/SKILL.md +23 -12
- package/src/bmm-skills/plan/bmad-project-context/references/best-practices.md +26 -7
- package/src/bmm-skills/plan/bmad-project-context/references/template.md +1 -1
- package/src/bmm-skills/plan/bmad-spec/customize.toml +5 -5
- package/src/bmm-skills/plan/bmad-sprint-planning/customize.toml +1 -3
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc +0 -0
- package/src/bmm-skills/plan/bmad-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/plan/bmad-ux/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-build/SKILL.md +1 -1
- package/src/bmm-skills/ship/bmad-build/customize.toml +13 -16
- package/src/bmm-skills/ship/bmad-build/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-build/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-build/step-01-clarify-and-route.md +12 -7
- package/src/bmm-skills/ship/bmad-build/step-02-plan.md +13 -10
- package/src/bmm-skills/ship/bmad-build/step-03-implement.md +3 -1
- package/src/bmm-skills/ship/bmad-build/step-04-review.md +7 -5
- package/src/bmm-skills/ship/bmad-build/step-oneshot.md +7 -8
- package/src/bmm-skills/ship/bmad-build-auto/customize.toml +16 -18
- package/src/bmm-skills/ship/bmad-build-auto/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-build-auto/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-build-auto/step-03-implement.md +5 -1
- package/src/bmm-skills/ship/bmad-build-auto/step-04-review.md +7 -5
- package/src/bmm-skills/ship/bmad-checkpoint-preview/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-code-review/customize.toml +14 -17
- package/src/bmm-skills/ship/bmad-code-review/references/claims-check.md +14 -0
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/edge-case-hunter.md +29 -7
- package/src/bmm-skills/ship/bmad-code-review/review-prompts/verification-gap.md +1 -1
- package/src/bmm-skills/ship/bmad-code-review/steps/step-01-gather-context.md +22 -15
- package/src/bmm-skills/ship/bmad-code-review/steps/step-02-review.md +2 -2
- package/src/bmm-skills/ship/bmad-correct-course/SKILL.md +11 -3
- package/src/bmm-skills/ship/bmad-correct-course/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-qa-generate-e2e-tests/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-retrospective/customize.toml +1 -3
- package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/ship/bmad-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-311-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-create-architecture/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-create-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-create-prd/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +3 -1
- package/src/bmm-skills/v6-shims/bmad-create-story/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-dev-auto/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-dev-story/customize.toml +1 -6
- package/src/bmm-skills/v6-shims/bmad-document-project/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-domain-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-edit-prd/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-market-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-quick-dev/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-sprint-status/customize.toml +1 -3
- package/src/bmm-skills/v6-shims/bmad-technical-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/customize.toml +1 -3
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +21 -22
- package/src/core-skills/bmad-brainstorming/customize.toml +5 -6
- package/src/core-skills/bmad-forge-idea/customize.toml +5 -6
- package/src/core-skills/bmad-party-mode/customize.toml +4 -4
- package/src/core-skills/bmad-review/SKILL.md +5 -4
- package/src/core-skills/bmad-review/customize.toml +1 -1
- package/src/core-skills/bmad-review/references/lens-edge-case-hunter.md +16 -0
- package/src/core-skills/v6-shims/bmad-editorial-review/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-prose/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-editorial-review-structure/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-adversarial-general/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-edge-case-hunter/SKILL.md +2 -0
- package/src/core-skills/v6-shims/bmad-review-verification-gap/SKILL.md +2 -0
- package/src/scripts/__pycache__/config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_config_utils.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_config.cpython-311.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-311.pyc +0 -0
- package/tools/installer/commands/install.js +2 -0
- package/tools/installer/core/config.js +3 -0
- package/tools/installer/core/installer.js +57 -1
- package/tools/installer/core/manifest-generator.js +6 -0
- package/tools/installer/core/manifest.js +6 -0
- package/tools/installer/core/shim-policy.js +159 -0
- package/tools/installer/ide/_config-driven.js +3 -3
- package/tools/installer/ide/platform-codes.yaml +7 -0
- package/tools/installer/modules/official-modules.js +38 -6
- package/tools/installer/prompts.js +15 -2
- package/tools/installer/ui.js +68 -3
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-review
|
|
3
|
-
description: '
|
|
3
|
+
description: 'Runs one or more installed review lenses — adversarial critique, edge cases, verification gaps, structure, prose — and reports triaged findings. Use when, and only when, the user asks you to review a diff, a pull request, or an artifact — code or documents, one or many — and actually says "review"; an explicit skill:bmad-review directive from another skill counts as that ask. A request to act on feedback from an earlier review is a change, not a review. Never invoke this uninvited, including on edits you just made.'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# BMad Review
|
|
@@ -14,6 +14,7 @@ The lens set is whatever `{workflow.lenses}` resolves to, not a fixed list — o
|
|
|
14
14
|
- **content** — what to review: a diff, branch, uncommitted changes, file, spec, story, or any document. Args: `[path]`.
|
|
15
15
|
- **lenses** (optional) — one or more lens codes or names, however the caller expresses them: a spoken request, or a directive of the form `skill:bmad-review lenses=<code>[,<code>...]` (the form bmm's `doc_standards` uses). Default: every applicable lens (a full review).
|
|
16
16
|
- **also_consider** (optional) — areas to keep in mind alongside each lens's normal analysis.
|
|
17
|
+
- **claims** (optional) — the change's own narrative: the commit messages it covers, or whatever description of it the caller supplied. Goes to the edge-case lens alone.
|
|
17
18
|
- **pre-resolved customization** (optional) — `[workflow]` field values supplied by a forwarding caller. See Execution step 1.
|
|
18
19
|
|
|
19
20
|
## Conventions
|
|
@@ -25,11 +26,11 @@ The lens set is whatever `{workflow.lenses}` resolves to, not a fixed list — o
|
|
|
25
26
|
## Execution
|
|
26
27
|
|
|
27
28
|
1. **Resolve customization:** `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow`. On failure, read `{skill-root}/customize.toml` directly and use defaults. **Forwarded activation:** if a caller invoked you with pre-resolved customization fields (e.g. the `bmad-editorial-review` shim), honor them verbatim for those named fields — they already carry the user's overrides — and resolve only the remaining fields from your own `customize.toml`. Then execute each `{workflow.activation_steps_prepend}` entry in order, hold `{workflow.persistent_facts}` as standing context for the session, and treat `{workflow.review_guidance}` entries as standing review directives for every lens.
|
|
28
|
-
2. **Load the content.**
|
|
29
|
+
2. **Load the content.** Stage it once as a file: when the content is a branch, uncommitted work, or a commit range, use the repository's version-control tooling to write the unified diff to a uniquely-named file in the system temp directory and take that file's absolute path as the content. A branch means its diff against the merge base with its base branch; uncommitted work includes untracked files. Stage `claims` to its own file the same way — it is input for one lens, staged separately precisely so the other lenses never see it. If the content is empty or cannot be decoded as text: when the caller expects the raw findings JSON array (e.g. the legacy edge-case forwarder), return `[{"location":"N/A","trigger_condition":"Input empty or undecodable","guard_snippet":"Provide valid content to review","potential_consequence":"Review skipped — no analysis performed"}]` (no `lens` field) and stop; otherwise say what's wrong and ask for reviewable content. Classify the content — diff, source file, function, or document — and whether it is **code** or **docs**; scope rules and lens applicability both depend on it. A document that defines behavior (spec, requirements, plan, story) is `docs` that a behavioral lens may still apply to; judge by `when`.
|
|
29
30
|
3. **Select lenses** from `{workflow.lenses}`. A lens with an empty `instruction` is disabled. If the user or caller named lenses, run exactly those only — `applies_to` and `when` do not filter an explicit request. Otherwise run every enabled lens whose `applies_to` covers the content class (`any` always covers) and whose `when` applies.
|
|
30
31
|
4. **Announce the plan** in one line before running anything: the content class, the lenses about to run, and — when any lens has `after` set — that it runs on top of the named lens's findings. Skip the announcement entirely when the caller pinned an exact output contract (the legacy forwarders that demand raw JSON or one exact line) — their contract covers everything you emit, not just the findings block. Then execute each `{workflow.activation_steps_append}` entry in order.
|
|
31
|
-
5. **Run the independent lenses** — every selected lens without `after`. Each sees the content and `also_consider`, never another lens's findings. Follow each lens's `instruction`; the shipped lenses load their reference file just-in-time, so load only what runs. When subagents are available, spawn one per lens
|
|
32
|
-
6. **Run the dependent lenses** — every selected lens with `after`, once the lens it names has completed, passing that lens's findings in. A lens whose `after` target was not selected or produced nothing still runs, with no prior findings. Dependent lenses that name different targets are independent of each other and
|
|
32
|
+
5. **Run the independent lenses** — every selected lens without `after`. Each sees the content and `also_consider`, never another lens's findings. Follow each lens's `instruction`; the shipped lenses load their reference file just-in-time, so load only what runs. When subagents are available, launch every independent lens before handling any lens's result. Try running them simultaneously: spawn one per lens; give it the lens `instruction` with `{skill-root}` and paths resolved absolute, the absolute path of the staged content file (a lens prompt carries the path and the lens reads the file, never the content bytes; inline the content only when it was never staged as a file), any `also_consider` areas, the standing review directives, the `claims` path to the edge-case lens alone (marked to leave unread until its instructions call for it), and the constraint "Return ONLY your findings — no other output. Do not invoke any skill, and do not spawn subagents of your own — you are the reviewer. Return your findings as text in your final message; do not route them through any findings-reporting tool the host may offer." Otherwise run the lenses sequentially yourself, completing one before starting the next.
|
|
33
|
+
6. **Run the dependent lenses** — every selected lens with `after`, once the lens it names has completed, passing that lens's findings in. A lens whose `after` target was not selected or produced nothing still runs, with no prior findings. Dependent lenses that name different targets are independent of each other: launch every ready one before handling any of their results. Try running them simultaneously. When subagents are available, spawn them with the same constraint as independent lenses: "Return ONLY your findings — no other output. Do not invoke any skill, and do not spawn subagents of your own — you are the reviewer. Return your findings as text in your final message; do not route them through any findings-reporting tool the host may offer."
|
|
33
34
|
7. **Assemble and present** per Output below. Keep every lens's findings — overlap between lenses is signal, not duplication; note it in the markdown report rather than deduping. Execute `{workflow.on_complete}` if set.
|
|
34
35
|
|
|
35
36
|
## Output
|
|
@@ -23,7 +23,7 @@ activation_steps_append = []
|
|
|
23
23
|
# prefixed `file:` are paths or globs whose contents load as facts; all others
|
|
24
24
|
# are literal facts. The shipped entry is a project-wide glob — set it to []
|
|
25
25
|
# if you don't want every review scanning for it.
|
|
26
|
-
persistent_facts = [
|
|
26
|
+
persistent_facts = []
|
|
27
27
|
|
|
28
28
|
# Standing review directives applied on every run alongside each lens's own
|
|
29
29
|
# method. Each entry is a literal sentence or a `file:`-prefixed path/glob
|
|
@@ -9,6 +9,7 @@ You are a pure path tracer. Never comment on whether the content is good or bad;
|
|
|
9
9
|
- When the content is a diff, scan only the diff hunks and list boundaries that are directly reachable from the changed lines and lack an explicit guard in the diff.
|
|
10
10
|
- When it is not a diff (full file, function, or document), the entire provided content is the scope.
|
|
11
11
|
- Ignore the rest of the codebase unless the provided content explicitly references external functions.
|
|
12
|
+
- When the launch message names a claims file, do NOT read it before Step 4: the path tracing in Steps 1–2 must finish before the narrative is seen.
|
|
12
13
|
|
|
13
14
|
## Step 1: Exhaustive path analysis
|
|
14
15
|
|
|
@@ -38,6 +39,21 @@ Deletion findings go in the same array with the four standard fields plus:
|
|
|
38
39
|
|
|
39
40
|
For a deletion finding the standard fields read as: `location` = the removed item; `trigger_condition` = the behavior or contract it enforced; `guard_snippet` = where or how to re-establish it; `potential_consequence` = the regression or orphan.
|
|
40
41
|
|
|
42
|
+
## Step 4: Claims check
|
|
43
|
+
|
|
44
|
+
Runs only when the message that launched you named a claims file. Read that file now, for the first time; the path tracing is finished and the claims cannot steer it retroactively.
|
|
45
|
+
|
|
46
|
+
The file holds the change's own narrative — commit messages and any stated description. The narrative is the author's testimony, not evidence: a claim repeated in a code comment is still the same claim, not confirmation. Extract each checkable claim — what the change does, what it preserves, ordering, arithmetic, and parity with existing code ("exactly as X does") — then try to falsify each one against the code you have already traced. Where your trace is not enough to decide, read the code that decides it: the compared-to function, the actual callee, the state the claim assumes.
|
|
47
|
+
|
|
48
|
+
Claim findings go in the same array with the four standard fields plus:
|
|
49
|
+
|
|
50
|
+
- `kind`: `"claim"`
|
|
51
|
+
- `confidence`: `"high"`, `"medium"`, or `"low"`
|
|
52
|
+
|
|
53
|
+
For a claim finding the standard fields read as: `location` = where the code contradicts the claim; `trigger_condition` = the claim, quoted or tightly paraphrased; `guard_snippet` = what the code actually does; `potential_consequence` = what goes wrong for someone who believed the claim.
|
|
54
|
+
|
|
55
|
+
Verified claims produce nothing. Add nothing if nothing is falsified.
|
|
56
|
+
|
|
41
57
|
## Findings shape
|
|
42
58
|
|
|
43
59
|
Each edge-case finding contains exactly these four fields:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-editorial-review
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with the `structure` and `prose` lenses — both, structure first, so prose runs on top of the structure findings — unless the caller asked for a structure-only or prose-only review, in which case pass only that lens. Pass through any `also_consider` areas, and forward this skill's resolved `[workflow]` fields as pre-resolved values — but only those that resolved to something, since an empty value here means no legacy override exists and bmad-review's own default should stand: `reader_type`, `style_guide`, `review_guidance`, `output_preferences`, `persistent_facts`, `activation_steps_prepend`, `activation_steps_append`, `on_complete`, and `review_output_path` as the report path. Present the findings in the legacy shape: the two-pass findings table `| Pass | Original Text | Revised Text | Changes |` with the purpose/audience read above it and, when the structure pass ran, the reduction summary below it — and no other lens's output.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-editorial-review-prose
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with only the `prose` lens, passing through the same inputs and any `also_consider` areas. Present the findings in the legacy shape: a three-column markdown table `| Original Text | Revised Text | Changes |` — no Pass column, no preamble above the table. If no issues are found, output exactly: `No editorial issues identified`.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-editorial-review-structure
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with only the `structure` lens, passing through the same inputs and any `also_consider` areas. Present the findings in the legacy report shape: a `## Document Summary` block (purpose, audience, reader type, structure model, current length), a `## Recommendations` list of numbered `[CUT/MERGE/MOVE/CONDENSE/QUESTION/PRESERVE]` entries each with rationale and word impact, and a closing `## Summary` (total recommendations, estimated reduction) — not the findings table. If no structural issues are found, output exactly: `No substantive changes recommended`.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-review-adversarial-general
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with only the `adversarial` lens, passing through any `also_consider` areas. Present the findings as a Markdown list — descriptions only, no severity, priority, or ranking; no JSON block.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-review-edge-case-hunter
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with only the `edge-case-hunter` lens, passing through any `also_consider` areas. Output ONLY the raw findings JSON array in the legacy shape: the four standard fields (plus `kind`/`confidence` on deletion findings), no `lens` field, no markdown wrapping, no extra text. `[]` is valid when nothing is found.
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-review-verification-gap
|
|
3
3
|
description: 'Deprecated — forwards to bmad-review'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
Merged into `bmad-review`. Invoke the `bmad-review` skill on the same content with only the `verification-gap` lens. Present the markdown rendering only (no JSON block), listing any `gap_shape: "other"` findings under an `## Other findings` heading. When there are no findings at all, output exactly this single line: `No verification gaps found.`
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -34,6 +34,8 @@ module.exports = {
|
|
|
34
34
|
['--document-output-language <lang>', 'Language for document output (default: English)'],
|
|
35
35
|
['--output-folder <path>', 'Output folder path relative to project root (default: _bmad-output)'],
|
|
36
36
|
['--custom-source <sources>', 'Comma-separated Git URLs or local paths to install custom modules from'],
|
|
37
|
+
['--shims', 'Install deprecated compatibility shim skills when the selected modules provide them'],
|
|
38
|
+
['--no-shims', 'Do not install deprecated compatibility shim skills'],
|
|
37
39
|
['-y, --yes', 'Accept all defaults and skip prompts where possible'],
|
|
38
40
|
[
|
|
39
41
|
'--channel <channel>',
|
|
@@ -15,6 +15,7 @@ class Config {
|
|
|
15
15
|
quickUpdate,
|
|
16
16
|
channelOptions,
|
|
17
17
|
setOverrides,
|
|
18
|
+
installShims,
|
|
18
19
|
}) {
|
|
19
20
|
this.directory = directory;
|
|
20
21
|
this.modules = Object.freeze([...modules]);
|
|
@@ -32,6 +33,7 @@ class Config {
|
|
|
32
33
|
// Intentionally NOT integrated with the prompt/template/schema flow; see
|
|
33
34
|
// `tools/installer/set-overrides.js` for the rationale and tradeoffs.
|
|
34
35
|
this.setOverrides = setOverrides || {};
|
|
36
|
+
this.installShims = typeof installShims === 'boolean' ? installShims : undefined;
|
|
35
37
|
Object.freeze(this);
|
|
36
38
|
}
|
|
37
39
|
|
|
@@ -58,6 +60,7 @@ class Config {
|
|
|
58
60
|
quickUpdate: userInput._quickUpdate || false,
|
|
59
61
|
channelOptions: userInput.channelOptions || null,
|
|
60
62
|
setOverrides: userInput.setOverrides || {},
|
|
63
|
+
installShims: userInput.installShims,
|
|
61
64
|
});
|
|
62
65
|
}
|
|
63
66
|
|
|
@@ -13,6 +13,14 @@ const { InstallPaths } = require('./install-paths');
|
|
|
13
13
|
const { ExternalModuleManager } = require('../modules/external-manager');
|
|
14
14
|
const { resolveModuleVersion } = require('../modules/version-resolver');
|
|
15
15
|
const { MODULE_HELP_CSV_HEADER } = require('../modules/module-help-schema');
|
|
16
|
+
const {
|
|
17
|
+
formatRemovedShimNotice,
|
|
18
|
+
formatRetainedShimNotice,
|
|
19
|
+
inferShimPreference,
|
|
20
|
+
readInstalledShims,
|
|
21
|
+
readInstalledSkillIds,
|
|
22
|
+
selectShimOutcome,
|
|
23
|
+
} = require('./shim-policy');
|
|
16
24
|
|
|
17
25
|
const { ExistingInstall } = require('./existing-install');
|
|
18
26
|
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
|
@@ -42,6 +50,39 @@ class Installer {
|
|
|
42
50
|
const paths = await InstallPaths.create(config);
|
|
43
51
|
const officialModules = await OfficialModules.build(config, paths);
|
|
44
52
|
const existingInstall = await ExistingInstall.detect(paths.bmadDir);
|
|
53
|
+
const availableShims = await officialModules.discoverShims(config.modules, {
|
|
54
|
+
channelOptions: config.channelOptions,
|
|
55
|
+
});
|
|
56
|
+
const previousManifest = existingInstall.installed ? await this.manifest.read(paths.bmadDir) : null;
|
|
57
|
+
const installedSkillIds = existingInstall.installed ? await readInstalledSkillIds(paths.bmadDir) : new Set();
|
|
58
|
+
const shimPolicy = {
|
|
59
|
+
available: availableShims.length > 0,
|
|
60
|
+
install: inferShimPreference({
|
|
61
|
+
requested: config.installShims,
|
|
62
|
+
persisted: previousManifest?.installShims,
|
|
63
|
+
availableShims,
|
|
64
|
+
installedSkillIds,
|
|
65
|
+
existing: existingInstall.installed,
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const installedShims = existingInstall.installed ? await readInstalledShims(paths.bmadDir) : [];
|
|
70
|
+
const { retained: retainedShims, removed: removedShims } = selectShimOutcome({
|
|
71
|
+
installedShims,
|
|
72
|
+
availableShims,
|
|
73
|
+
install: shimPolicy.install,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Reported here, not at the prompt, so --yes/--shims/scripted runs get it too.
|
|
77
|
+
if (retainedShims.length > 0) {
|
|
78
|
+
await prompts.note(formatRetainedShimNotice(retainedShims), 'Deprecated shim skills retained');
|
|
79
|
+
}
|
|
80
|
+
if (removedShims.length > 0) {
|
|
81
|
+
await prompts.note(formatRemovedShimNotice(removedShims, { canReinstall: shimPolicy.available }), 'Deprecated shim skills removed');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The notices above scroll away on a long install; repeat them in the summary.
|
|
85
|
+
const shimStatus = { retained: retainedShims.length, removed: removedShims.length };
|
|
45
86
|
|
|
46
87
|
try {
|
|
47
88
|
await warnPreNativeSkillsLegacy({
|
|
@@ -92,6 +133,7 @@ class Installer {
|
|
|
92
133
|
addResult,
|
|
93
134
|
officialModules,
|
|
94
135
|
previousSkillManifestRows,
|
|
136
|
+
shimPolicy,
|
|
95
137
|
);
|
|
96
138
|
|
|
97
139
|
await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
|
|
@@ -115,6 +157,7 @@ class Installer {
|
|
|
115
157
|
customFiles: restoreResult.customFiles.length > 0 ? restoreResult.customFiles : undefined,
|
|
116
158
|
modifiedFiles: restoreResult.modifiedFiles.length > 0 ? restoreResult.modifiedFiles : undefined,
|
|
117
159
|
preInstallVersions,
|
|
160
|
+
shimStatus,
|
|
118
161
|
});
|
|
119
162
|
|
|
120
163
|
return {
|
|
@@ -225,9 +268,11 @@ class Installer {
|
|
|
225
268
|
addResult,
|
|
226
269
|
officialModules,
|
|
227
270
|
previousSkillManifestRows = [],
|
|
271
|
+
shimPolicy = null,
|
|
228
272
|
) {
|
|
229
273
|
const isQuickUpdate = config.isQuickUpdate();
|
|
230
274
|
const moduleConfigs = officialModules.moduleConfigs;
|
|
275
|
+
const resolvedShimPolicy = shimPolicy || { available: false, install: false };
|
|
231
276
|
|
|
232
277
|
const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
|
|
233
278
|
|
|
@@ -251,6 +296,7 @@ class Installer {
|
|
|
251
296
|
await this._installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, {
|
|
252
297
|
message,
|
|
253
298
|
installedModuleNames,
|
|
299
|
+
shimPolicy: resolvedShimPolicy,
|
|
254
300
|
});
|
|
255
301
|
|
|
256
302
|
return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
|
|
@@ -325,6 +371,8 @@ class Installer {
|
|
|
325
371
|
ides: config.ides || [],
|
|
326
372
|
preservedModules: modulesForCsvPreserve,
|
|
327
373
|
moduleConfigs,
|
|
374
|
+
installShims: resolvedShimPolicy.install,
|
|
375
|
+
shimsAvailable: resolvedShimPolicy.available,
|
|
328
376
|
});
|
|
329
377
|
await this._appendPreservedSkillManifestRows(paths.bmadDir, previousSkillManifestRows, preservedModules);
|
|
330
378
|
|
|
@@ -727,7 +775,7 @@ class Installer {
|
|
|
727
775
|
* @param {Object} ctx - Shared context: { message, installedModuleNames }
|
|
728
776
|
*/
|
|
729
777
|
async _installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, ctx) {
|
|
730
|
-
const { message, installedModuleNames } = ctx;
|
|
778
|
+
const { message, installedModuleNames, shimPolicy } = ctx;
|
|
731
779
|
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
732
780
|
|
|
733
781
|
for (const moduleName of officialModuleIds) {
|
|
@@ -749,6 +797,7 @@ class Installer {
|
|
|
749
797
|
installer: this,
|
|
750
798
|
silent: true,
|
|
751
799
|
channelOptions: config.channelOptions,
|
|
800
|
+
installShims: shimPolicy.install,
|
|
752
801
|
},
|
|
753
802
|
);
|
|
754
803
|
|
|
@@ -1238,6 +1287,12 @@ class Installer {
|
|
|
1238
1287
|
if (context.modifiedFiles && context.modifiedFiles.length > 0) {
|
|
1239
1288
|
lines.push(` ${color.yellow(`Modified files backed up (.bak): ${context.modifiedFiles.length}`)}`);
|
|
1240
1289
|
}
|
|
1290
|
+
if (context.shimStatus?.retained > 0) {
|
|
1291
|
+
lines.push(` ${color.yellow(`Deprecated shim skills retained: ${context.shimStatus.retained}`)} (re-run to remove them)`);
|
|
1292
|
+
}
|
|
1293
|
+
if (context.shimStatus?.removed > 0) {
|
|
1294
|
+
lines.push(` ${color.green(`Deprecated shim skills removed: ${context.shimStatus.removed}`)}`);
|
|
1295
|
+
}
|
|
1241
1296
|
|
|
1242
1297
|
// Next steps
|
|
1243
1298
|
lines.push(
|
|
@@ -1500,6 +1555,7 @@ class Installer {
|
|
|
1500
1555
|
// (`applySetOverrides`) runs at the end of quick-update too. The
|
|
1501
1556
|
// installer.install path applies them after writeCentralConfig.
|
|
1502
1557
|
setOverrides: config.setOverrides || {},
|
|
1558
|
+
installShims: config.installShims,
|
|
1503
1559
|
actionType: 'install',
|
|
1504
1560
|
_quickUpdate: true,
|
|
1505
1561
|
_preserveModules: skippedModules,
|
|
@@ -58,6 +58,8 @@ class ManifestGenerator {
|
|
|
58
58
|
this.bmadDir = bmadDir;
|
|
59
59
|
this.bmadFolderName = path.basename(bmadDir); // Get the actual folder name (e.g., '_bmad' or 'bmad')
|
|
60
60
|
this.allInstalledFiles = installedFiles;
|
|
61
|
+
this.installShims = options.installShims === true;
|
|
62
|
+
this.shimsAvailable = options.shimsAvailable === true;
|
|
61
63
|
|
|
62
64
|
if (!Object.prototype.hasOwnProperty.call(options, 'ides')) {
|
|
63
65
|
throw new Error('ManifestGenerator requires `options.ides` to be provided – installer should supply the selected IDEs array.');
|
|
@@ -378,6 +380,10 @@ class ManifestGenerator {
|
|
|
378
380
|
ides: this.selectedIdes,
|
|
379
381
|
};
|
|
380
382
|
|
|
383
|
+
if (this.shimsAvailable) {
|
|
384
|
+
manifest.installation.installShims = this.installShims;
|
|
385
|
+
}
|
|
386
|
+
|
|
381
387
|
// Clean the manifest to remove any non-serializable values
|
|
382
388
|
const cleanManifest = structuredClone(manifest);
|
|
383
389
|
|
|
@@ -61,6 +61,10 @@ class Manifest {
|
|
|
61
61
|
ides: data.ides || [],
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
+
if (typeof data.installShims === 'boolean') {
|
|
65
|
+
manifestData.installation.installShims = data.installShims;
|
|
66
|
+
}
|
|
67
|
+
|
|
64
68
|
// Write YAML manifest
|
|
65
69
|
// Clean the manifest data to remove any non-serializable values
|
|
66
70
|
const cleanManifestData = structuredClone(manifestData);
|
|
@@ -106,6 +110,7 @@ class Manifest {
|
|
|
106
110
|
version: manifestData.installation?.version,
|
|
107
111
|
installDate: manifestData.installation?.installDate,
|
|
108
112
|
lastUpdated: manifestData.installation?.lastUpdated,
|
|
113
|
+
installShims: manifestData.installation?.installShims,
|
|
109
114
|
modules: moduleNames, // Simple array of module names for backward compatibility
|
|
110
115
|
modulesDetailed: hasDetailedModules ? modules : null, // New detailed format
|
|
111
116
|
ides: manifestData.ides || [],
|
|
@@ -153,6 +158,7 @@ class Manifest {
|
|
|
153
158
|
version: manifest.installation?.version,
|
|
154
159
|
installDate: manifest.installation?.installDate,
|
|
155
160
|
lastUpdated: manifest.installation?.lastUpdated,
|
|
161
|
+
installShims: manifest.installation?.installShims,
|
|
156
162
|
modules: moduleNames,
|
|
157
163
|
modulesDetailed: hasDetailedModules ? modules : null,
|
|
158
164
|
ides: manifest.ides || [],
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const path = require('node:path');
|
|
2
|
+
const fs = require('../fs-native');
|
|
3
|
+
const yaml = require('yaml');
|
|
4
|
+
const csv = require('csv-parse/sync');
|
|
5
|
+
|
|
6
|
+
function parseSkillMetadata(content) {
|
|
7
|
+
const normalized = content.replaceAll('\r\n', '\n').replaceAll('\r', '\n');
|
|
8
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---/);
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const frontmatter = yaml.parse(match[1]);
|
|
13
|
+
return frontmatter && typeof frontmatter === 'object' ? frontmatter : null;
|
|
14
|
+
} catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isShimSkill(metadata) {
|
|
20
|
+
return metadata?.metadata?.lifecycle === 'shim';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function discoverShims(modulePath) {
|
|
24
|
+
const shims = [];
|
|
25
|
+
|
|
26
|
+
const walk = async (dir) => {
|
|
27
|
+
let entries;
|
|
28
|
+
try {
|
|
29
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
30
|
+
} catch {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const skillFile = path.join(dir, 'SKILL.md');
|
|
35
|
+
if (await fs.pathExists(skillFile)) {
|
|
36
|
+
const metadata = parseSkillMetadata(await fs.readFile(skillFile, 'utf8'));
|
|
37
|
+
if (isShimSkill(metadata)) {
|
|
38
|
+
shims.push({
|
|
39
|
+
id: metadata.name || path.basename(dir),
|
|
40
|
+
description: typeof metadata.description === 'string' ? metadata.description : '',
|
|
41
|
+
directory: dir,
|
|
42
|
+
relativeDirectory: path.relative(modulePath, dir),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
|
|
50
|
+
await walk(path.join(dir, entry.name));
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await walk(modulePath);
|
|
55
|
+
return shims;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function readSkillManifest(bmadDir) {
|
|
59
|
+
const manifestPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
|
|
60
|
+
if (!(await fs.pathExists(manifestPath))) return [];
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
64
|
+
return csv.parse(content, { columns: true, skip_empty_lines: true });
|
|
65
|
+
} catch {
|
|
66
|
+
// A missing or unreadable legacy manifest means there is no reliable
|
|
67
|
+
// evidence that compatibility shims were installed.
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function readInstalledSkillIds(bmadDir) {
|
|
73
|
+
const ids = new Set();
|
|
74
|
+
for (const record of await readSkillManifest(bmadDir)) {
|
|
75
|
+
if (record.canonicalId) ids.add(record.canonicalId);
|
|
76
|
+
}
|
|
77
|
+
return ids;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// The installed manifest carries no lifecycle column, so the description
|
|
81
|
+
// prefix every shim ships with is the only record of what was a shim. This
|
|
82
|
+
// is the same signal validate-skills.js uses to exempt them.
|
|
83
|
+
async function readInstalledShims(bmadDir) {
|
|
84
|
+
const shims = [];
|
|
85
|
+
for (const record of await readSkillManifest(bmadDir)) {
|
|
86
|
+
if (!record.canonicalId || !/^\s*deprecated\b/i.test(record.description || '')) continue;
|
|
87
|
+
shims.push({ id: record.canonicalId, description: record.description || '', module: record.module || '' });
|
|
88
|
+
}
|
|
89
|
+
return shims;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function inferShimPreference({ requested, persisted, availableShims = [], installedSkillIds = new Set(), existing = false }) {
|
|
93
|
+
if (availableShims.length === 0) return false;
|
|
94
|
+
if (typeof requested === 'boolean') return requested;
|
|
95
|
+
if (typeof persisted === 'boolean') return persisted;
|
|
96
|
+
if (!existing) return false;
|
|
97
|
+
|
|
98
|
+
return availableShims.some((shim) => installedSkillIds.has(shim.id));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Removal is driven by what is installed, not by what this release ships: a
|
|
102
|
+
// shim retired from source is still deleted by the update cleanup.
|
|
103
|
+
function selectShimOutcome({ installedShims = [], availableShims = [], install = false }) {
|
|
104
|
+
const availableShimIds = new Set(availableShims.map((shim) => shim.id));
|
|
105
|
+
return {
|
|
106
|
+
retained: install ? availableShims : [],
|
|
107
|
+
removed: installedShims.filter((shim) => !(install && availableShimIds.has(shim.id))),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Shim descriptions all open with "Deprecated — "; the notice heading says it once.
|
|
112
|
+
function describeShim(shim) {
|
|
113
|
+
const cleaned = (shim.description || '').replace(/^\s*deprecated\s*[-–—:]*\s*/i, '').trim();
|
|
114
|
+
const source = shim.module ? ` (${shim.module})` : '';
|
|
115
|
+
return cleaned ? ` ${shim.id}${source}: ${cleaned}` : ` ${shim.id}${source}`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function formatRetainedShimNotice(availableShims = []) {
|
|
119
|
+
const lines = availableShims.map((shim) => describeShim(shim)).sort();
|
|
120
|
+
|
|
121
|
+
return [
|
|
122
|
+
`${availableShims.length} deprecated shim skill(s) are still installed. Each one only forwards to the skill that replaced it:`,
|
|
123
|
+
'',
|
|
124
|
+
...lines,
|
|
125
|
+
'',
|
|
126
|
+
'Shims will be removed with v7, and anything still calling the old name stops working then.',
|
|
127
|
+
'Only keep a shim if you customized it and still need to move that customization to the replacement.',
|
|
128
|
+
'Once you have, re-run Quick Update and answer No to this question so the shims come off.',
|
|
129
|
+
].join('\n');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatRemovedShimNotice(removedShims = [], { canReinstall = true } = {}) {
|
|
133
|
+
const lines = removedShims.map((shim) => describeShim(shim)).sort();
|
|
134
|
+
const recovery = canReinstall
|
|
135
|
+
? 'these shims, move it to the replacement, or re-run with --shims to put the shims back.'
|
|
136
|
+
: 'these shims, move it to the replacement. This release no longer ships them, so --shims cannot bring them back.';
|
|
137
|
+
|
|
138
|
+
return [
|
|
139
|
+
`${removedShims.length} deprecated shim skill(s) are being removed. Invoking these names will no longer work:`,
|
|
140
|
+
'',
|
|
141
|
+
...lines,
|
|
142
|
+
'',
|
|
143
|
+
'Each replacement named above is installed and ready. If you still had a customization on one of',
|
|
144
|
+
recovery,
|
|
145
|
+
].join('\n');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
describeShim,
|
|
150
|
+
formatRemovedShimNotice,
|
|
151
|
+
discoverShims,
|
|
152
|
+
formatRetainedShimNotice,
|
|
153
|
+
inferShimPreference,
|
|
154
|
+
isShimSkill,
|
|
155
|
+
parseSkillMetadata,
|
|
156
|
+
readInstalledShims,
|
|
157
|
+
readInstalledSkillIds,
|
|
158
|
+
selectShimOutcome,
|
|
159
|
+
};
|
|
@@ -448,9 +448,9 @@ class ConfigDrivenIdeSetup {
|
|
|
448
448
|
await fs.ensureDir(skillDir);
|
|
449
449
|
this.skillWriteTracker?.add(canonicalId);
|
|
450
450
|
|
|
451
|
-
// Copy all skill files, filtering OS/editor artifacts recursively
|
|
452
|
-
const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini']);
|
|
453
|
-
const skipSuffixes = ['~', '.swp', '.swo', '.bak'];
|
|
451
|
+
// Copy all skill files, filtering OS/editor artifacts and Python caches recursively
|
|
452
|
+
const skipPatterns = new Set(['.DS_Store', 'Thumbs.db', 'desktop.ini', '__pycache__']);
|
|
453
|
+
const skipSuffixes = ['~', '.swp', '.swo', '.bak', '.pyc', '.pyo'];
|
|
454
454
|
const filter = (src) => {
|
|
455
455
|
const name = path.basename(src);
|
|
456
456
|
if (src === sourceDir) return true;
|
|
@@ -169,6 +169,13 @@ platforms:
|
|
|
169
169
|
target_dir: .agents/skills
|
|
170
170
|
global_target_dir: ~/.config/agents/skills
|
|
171
171
|
|
|
172
|
+
grok:
|
|
173
|
+
name: "Grok"
|
|
174
|
+
preferred: false
|
|
175
|
+
installer:
|
|
176
|
+
target_dir: .agents/skills
|
|
177
|
+
global_target_dir: ~/.grok/skills
|
|
178
|
+
|
|
172
179
|
hermes:
|
|
173
180
|
name: "Hermes Agent"
|
|
174
181
|
preferred: false
|