bmad-method 6.11.1-next.5 → 6.11.1-next.7
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/plan/bmad-generate-project-context/SKILL.md +2 -0
- 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/ship/bmad-build/step-oneshot.md +5 -6
- 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-prd/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-create-story/SKILL.md +2 -0
- 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-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-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-technical-research/SKILL.md +2 -0
- package/src/bmm-skills/v6-shims/bmad-validate-prd/SKILL.md +2 -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 +25 -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 +91 -0
- package/tools/installer/modules/official-modules.js +38 -6
- package/tools/installer/ui.js +46 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "bmad-method",
|
|
4
|
-
"version": "6.11.1-next.
|
|
4
|
+
"version": "6.11.1-next.7",
|
|
5
5
|
"description": "Breakthrough Method of Agile AI-driven Development",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agile",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"rebundle": "node tools/installer/bundlers/bundle-web.js rebundle",
|
|
45
45
|
"test": "npm run test:refs && npm run test:install && npm run test:urls && npm run test:site-url && npm run test:channels && npm run test:renderer && npm run test:retrospective && npm run test:sprint-planning && npm run test:skills && npm run lint && npm run lint:md && npm run format:check",
|
|
46
46
|
"test:channels": "node test/test-installer-channels.js",
|
|
47
|
-
"test:install": "node test/test-installation-components.js",
|
|
47
|
+
"test:install": "node test/test-installation-components.js && node test/test-shim-policy.js",
|
|
48
48
|
"test:refs": "node test/test-file-refs-csv.js",
|
|
49
49
|
"test:renderer": "uv run --python 3.11 python -m unittest src/scripts/tests/test_config_utils.py src/scripts/tests/test_resolve_config.py src/scripts/tests/test_resolve_customization.py && node test/test-build-auto-renderer.js",
|
|
50
50
|
"test:retrospective": "uv run --python 3.11 src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_git_evidence.py && uv run --python 3.11 src/bmm-skills/ship/bmad-retrospective/scripts/tests/test_sprint_status.py",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: bmad-generate-project-context
|
|
3
3
|
description: 'Deprecated — forwards to bmad-project-context. Use when the user says "generate project context" or "create project context"'
|
|
4
|
+
metadata:
|
|
5
|
+
lifecycle: shim
|
|
4
6
|
---
|
|
5
7
|
|
|
6
8
|
# DEPRECATED — forwards to bmad-project-context
|
package/src/bmm-skills/plan/bmad-sprint-planning/scripts/__pycache__/sprint_plan.cpython-311.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -25,18 +25,17 @@ If a layer's instruction requires subagents and none are available, for each suc
|
|
|
25
25
|
|
|
26
26
|
### Classify
|
|
27
27
|
|
|
28
|
-
Deduplicate all review findings
|
|
28
|
+
Deduplicate all review findings, then route each finding in this order:
|
|
29
29
|
|
|
30
|
-
- **patch** —
|
|
31
|
-
- **
|
|
30
|
+
- **patch** — Patch every finding caused or exposed by this change that shows a defect that actually occurs, missing coverage for a specific case, or a broken gate or convention — not a state nothing reaches — and whose smallest fix is trivial, adds no public surface, and guards no state the finding did not demonstrate. Apply that smallest fix immediately.
|
|
31
|
+
- **HALT** — HALT on every finding caused or exposed by this change that shows the same evidence but whose smallest fix fails any of those conditions. Present it to the human for decision before proceeding.
|
|
32
|
+
- **defer** — Defer every other real finding, including pre-existing issues and improvement ideas. Append one new entry to `{{.implementation_artifacts}}/deferred-work.md` using this format. Do not modify existing entries or look for duplicates.
|
|
32
33
|
```markdown
|
|
33
34
|
- source_spec: `{spec_file}`
|
|
34
35
|
summary: <one sentence>
|
|
35
36
|
evidence: <why this is real>
|
|
36
37
|
```
|
|
37
|
-
- **reject** — noise. Drop silently.
|
|
38
|
-
|
|
39
|
-
If a finding is caused by this change but too significant for a trivial patch, HALT and present it to the human for decision before proceeding.
|
|
38
|
+
- **reject** — Reject only noise. Drop silently.
|
|
40
39
|
|
|
41
40
|
### Generate Spec Trace
|
|
42
41
|
|
package/src/bmm-skills/ship/bmad-retrospective/scripts/__pycache__/sprint_status.cpython-311.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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,7 @@ 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 { inferShimPreference, readInstalledSkillIds } = require('./shim-policy');
|
|
16
17
|
|
|
17
18
|
const { ExistingInstall } = require('./existing-install');
|
|
18
19
|
const { warnPreNativeSkillsLegacy } = require('./legacy-warnings');
|
|
@@ -42,6 +43,21 @@ class Installer {
|
|
|
42
43
|
const paths = await InstallPaths.create(config);
|
|
43
44
|
const officialModules = await OfficialModules.build(config, paths);
|
|
44
45
|
const existingInstall = await ExistingInstall.detect(paths.bmadDir);
|
|
46
|
+
const availableShims = await officialModules.discoverShims(config.modules, {
|
|
47
|
+
channelOptions: config.channelOptions,
|
|
48
|
+
});
|
|
49
|
+
const previousManifest = existingInstall.installed ? await this.manifest.read(paths.bmadDir) : null;
|
|
50
|
+
const installedSkillIds = existingInstall.installed ? await readInstalledSkillIds(paths.bmadDir) : new Set();
|
|
51
|
+
const shimPolicy = {
|
|
52
|
+
available: availableShims.length > 0,
|
|
53
|
+
install: inferShimPreference({
|
|
54
|
+
requested: config.installShims,
|
|
55
|
+
persisted: previousManifest?.installShims,
|
|
56
|
+
availableShims,
|
|
57
|
+
installedSkillIds,
|
|
58
|
+
existing: existingInstall.installed,
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
45
61
|
|
|
46
62
|
try {
|
|
47
63
|
await warnPreNativeSkillsLegacy({
|
|
@@ -92,6 +108,7 @@ class Installer {
|
|
|
92
108
|
addResult,
|
|
93
109
|
officialModules,
|
|
94
110
|
previousSkillManifestRows,
|
|
111
|
+
shimPolicy,
|
|
95
112
|
);
|
|
96
113
|
|
|
97
114
|
await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
|
|
@@ -225,9 +242,11 @@ class Installer {
|
|
|
225
242
|
addResult,
|
|
226
243
|
officialModules,
|
|
227
244
|
previousSkillManifestRows = [],
|
|
245
|
+
shimPolicy = null,
|
|
228
246
|
) {
|
|
229
247
|
const isQuickUpdate = config.isQuickUpdate();
|
|
230
248
|
const moduleConfigs = officialModules.moduleConfigs;
|
|
249
|
+
const resolvedShimPolicy = shimPolicy || { available: false, install: false };
|
|
231
250
|
|
|
232
251
|
const dirResults = { createdDirs: [], movedDirs: [], createdWdsFolders: [] };
|
|
233
252
|
|
|
@@ -251,6 +270,7 @@ class Installer {
|
|
|
251
270
|
await this._installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, {
|
|
252
271
|
message,
|
|
253
272
|
installedModuleNames,
|
|
273
|
+
shimPolicy: resolvedShimPolicy,
|
|
254
274
|
});
|
|
255
275
|
|
|
256
276
|
return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
|
|
@@ -325,6 +345,8 @@ class Installer {
|
|
|
325
345
|
ides: config.ides || [],
|
|
326
346
|
preservedModules: modulesForCsvPreserve,
|
|
327
347
|
moduleConfigs,
|
|
348
|
+
installShims: resolvedShimPolicy.install,
|
|
349
|
+
shimsAvailable: resolvedShimPolicy.available,
|
|
328
350
|
});
|
|
329
351
|
await this._appendPreservedSkillManifestRows(paths.bmadDir, previousSkillManifestRows, preservedModules);
|
|
330
352
|
|
|
@@ -727,7 +749,7 @@ class Installer {
|
|
|
727
749
|
* @param {Object} ctx - Shared context: { message, installedModuleNames }
|
|
728
750
|
*/
|
|
729
751
|
async _installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, ctx) {
|
|
730
|
-
const { message, installedModuleNames } = ctx;
|
|
752
|
+
const { message, installedModuleNames, shimPolicy } = ctx;
|
|
731
753
|
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
732
754
|
|
|
733
755
|
for (const moduleName of officialModuleIds) {
|
|
@@ -749,6 +771,7 @@ class Installer {
|
|
|
749
771
|
installer: this,
|
|
750
772
|
silent: true,
|
|
751
773
|
channelOptions: config.channelOptions,
|
|
774
|
+
installShims: shimPolicy.install,
|
|
752
775
|
},
|
|
753
776
|
);
|
|
754
777
|
|
|
@@ -1500,6 +1523,7 @@ class Installer {
|
|
|
1500
1523
|
// (`applySetOverrides`) runs at the end of quick-update too. The
|
|
1501
1524
|
// installer.install path applies them after writeCentralConfig.
|
|
1502
1525
|
setOverrides: config.setOverrides || {},
|
|
1526
|
+
installShims: config.installShims,
|
|
1503
1527
|
actionType: 'install',
|
|
1504
1528
|
_quickUpdate: true,
|
|
1505
1529
|
_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,91 @@
|
|
|
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
|
+
directory: dir,
|
|
41
|
+
relativeDirectory: path.relative(modulePath, dir),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (!entry.isDirectory() || entry.name.startsWith('.') || entry.name.startsWith('_')) continue;
|
|
49
|
+
await walk(path.join(dir, entry.name));
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
await walk(modulePath);
|
|
54
|
+
return shims;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function readInstalledSkillIds(bmadDir) {
|
|
58
|
+
const ids = new Set();
|
|
59
|
+
const manifestPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
|
|
60
|
+
if (!(await fs.pathExists(manifestPath))) return ids;
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const content = await fs.readFile(manifestPath, 'utf8');
|
|
64
|
+
const records = csv.parse(content, { columns: true, skip_empty_lines: true });
|
|
65
|
+
for (const record of records) {
|
|
66
|
+
if (record.canonicalId) ids.add(record.canonicalId);
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// A missing or unreadable legacy manifest means there is no reliable
|
|
70
|
+
// evidence that compatibility shims were installed.
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return ids;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function inferShimPreference({ requested, persisted, availableShims = [], installedSkillIds = new Set(), existing = false }) {
|
|
77
|
+
if (availableShims.length === 0) return false;
|
|
78
|
+
if (typeof requested === 'boolean') return requested;
|
|
79
|
+
if (typeof persisted === 'boolean') return persisted;
|
|
80
|
+
if (!existing) return false;
|
|
81
|
+
|
|
82
|
+
return availableShims.some((shim) => installedSkillIds.has(shim.id));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
discoverShims,
|
|
87
|
+
inferShimPreference,
|
|
88
|
+
isShimSkill,
|
|
89
|
+
parseSkillMetadata,
|
|
90
|
+
readInstalledSkillIds,
|
|
91
|
+
};
|
|
@@ -5,6 +5,7 @@ const prompts = require('../prompts');
|
|
|
5
5
|
const { getProjectRoot, getSourcePath, getModulePath } = require('../project-root');
|
|
6
6
|
const { CLIUtils } = require('../cli-utils');
|
|
7
7
|
const { ExternalModuleManager } = require('./external-manager');
|
|
8
|
+
const { discoverShims } = require('../core/shim-policy');
|
|
8
9
|
|
|
9
10
|
class OfficialModules {
|
|
10
11
|
constructor(options = {}) {
|
|
@@ -131,6 +132,23 @@ class OfficialModules {
|
|
|
131
132
|
return { modules };
|
|
132
133
|
}
|
|
133
134
|
|
|
135
|
+
async discoverShims(moduleNames = [], options = {}) {
|
|
136
|
+
const shims = [];
|
|
137
|
+
|
|
138
|
+
for (const moduleName of moduleNames) {
|
|
139
|
+
const sourcePath = await this.findModuleSource(moduleName, {
|
|
140
|
+
silent: true,
|
|
141
|
+
channelOptions: options.channelOptions,
|
|
142
|
+
});
|
|
143
|
+
if (!sourcePath) continue;
|
|
144
|
+
|
|
145
|
+
const moduleShims = await discoverShims(sourcePath);
|
|
146
|
+
for (const shim of moduleShims) shims.push({ ...shim, module: moduleName });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return shims;
|
|
150
|
+
}
|
|
151
|
+
|
|
134
152
|
/**
|
|
135
153
|
* Get module information from a module path
|
|
136
154
|
* @param {string} modulePath - Path to the module directory
|
|
@@ -296,9 +314,13 @@ class OfficialModules {
|
|
|
296
314
|
`Aborting to avoid a partial install with no skills.`,
|
|
297
315
|
);
|
|
298
316
|
}
|
|
299
|
-
await this._copyResolvedSkills(pluginResolution, targetPath, fileTrackingCallback, options.moduleConfig
|
|
317
|
+
await this._copyResolvedSkills(pluginResolution, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
318
|
+
installShims: options.installShims,
|
|
319
|
+
});
|
|
300
320
|
} else {
|
|
301
|
-
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig
|
|
321
|
+
await this.copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
322
|
+
installShims: options.installShims,
|
|
323
|
+
});
|
|
302
324
|
}
|
|
303
325
|
|
|
304
326
|
if (!options.skipModuleInstaller) {
|
|
@@ -336,7 +358,7 @@ class OfficialModules {
|
|
|
336
358
|
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
337
359
|
* @param {Object} moduleConfig - Module configuration passed to copy filtering
|
|
338
360
|
*/
|
|
339
|
-
async _copyResolvedSkills(resolved, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
|
|
361
|
+
async _copyResolvedSkills(resolved, targetPath, fileTrackingCallback = null, moduleConfig = {}, installOptions = {}) {
|
|
340
362
|
await fs.ensureDir(targetPath);
|
|
341
363
|
|
|
342
364
|
// Copy each skill directory, flattened by leaf name. Leaf names must be
|
|
@@ -354,7 +376,7 @@ class OfficialModules {
|
|
|
354
376
|
}
|
|
355
377
|
seenLeaves.set(skillDirName, skillPath);
|
|
356
378
|
const skillTarget = path.join(targetPath, skillDirName);
|
|
357
|
-
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig);
|
|
379
|
+
await this.copyModuleWithFiltering(skillPath, skillTarget, fileTrackingCallback, moduleConfig, installOptions);
|
|
358
380
|
}
|
|
359
381
|
|
|
360
382
|
// Place module-help.csv at the module root.
|
|
@@ -385,7 +407,9 @@ class OfficialModules {
|
|
|
385
407
|
await fs.remove(targetPath);
|
|
386
408
|
}
|
|
387
409
|
|
|
388
|
-
await this._copyResolvedSkills(resolved, targetPath, fileTrackingCallback, options.moduleConfig
|
|
410
|
+
await this._copyResolvedSkills(resolved, targetPath, fileTrackingCallback, options.moduleConfig, {
|
|
411
|
+
installShims: options.installShims,
|
|
412
|
+
});
|
|
389
413
|
|
|
390
414
|
// Create directories declared in module.yaml (strategies 1-4 may have these)
|
|
391
415
|
if (!options.skipModuleInstaller) {
|
|
@@ -522,11 +546,19 @@ class OfficialModules {
|
|
|
522
546
|
* @param {Function} fileTrackingCallback - Optional callback to track installed files
|
|
523
547
|
* @param {Object} moduleConfig - Module configuration with conditional flags
|
|
524
548
|
*/
|
|
525
|
-
async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}) {
|
|
549
|
+
async copyModuleWithFiltering(sourcePath, targetPath, fileTrackingCallback = null, moduleConfig = {}, installOptions = {}) {
|
|
526
550
|
// Get all files in source
|
|
527
551
|
const sourceFiles = await this.getFileList(sourcePath);
|
|
552
|
+
const shimDirectories =
|
|
553
|
+
installOptions.installShims === false
|
|
554
|
+
? (await discoverShims(sourcePath)).map((shim) => shim.relativeDirectory.split(path.sep).join('/'))
|
|
555
|
+
: [];
|
|
528
556
|
|
|
529
557
|
for (const file of sourceFiles) {
|
|
558
|
+
const normalizedFile = file.split(path.sep).join('/');
|
|
559
|
+
if (shimDirectories.some((shimDir) => shimDir === '' || normalizedFile === shimDir || normalizedFile.startsWith(`${shimDir}/`))) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
530
562
|
// Skip sub-modules directory - these are IDE-specific and handled separately
|
|
531
563
|
if (file.startsWith('sub-modules/')) {
|
|
532
564
|
continue;
|
package/tools/installer/ui.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
const channelResolver = require('./modules/channel-resolver');
|
|
18
18
|
const prompts = require('./prompts');
|
|
19
19
|
const { parseSetEntries } = require('./set-overrides');
|
|
20
|
+
const { inferShimPreference, readInstalledSkillIds } = require('./core/shim-policy');
|
|
20
21
|
|
|
21
22
|
const manifest = new Manifest();
|
|
22
23
|
|
|
@@ -110,6 +111,33 @@ async function getModuleVersion(moduleCode, { repoUrl = null, registryDefault =
|
|
|
110
111
|
* UI utilities for the installer
|
|
111
112
|
*/
|
|
112
113
|
class UI {
|
|
114
|
+
async _selectShimPreference({ selectedModules, bmadDir, existing, options, channelOptions }) {
|
|
115
|
+
const { OfficialModules } = require('./modules/official-modules');
|
|
116
|
+
const officialModules = new OfficialModules({ channelOptions });
|
|
117
|
+
const availableShims = await officialModules.discoverShims(selectedModules, { channelOptions });
|
|
118
|
+
|
|
119
|
+
// The prompt is capability-driven. Once the last shim leaves the incoming
|
|
120
|
+
// release this becomes an ordinary empty set, regardless of old state.
|
|
121
|
+
if (availableShims.length === 0) return;
|
|
122
|
+
|
|
123
|
+
const previousManifest = existing ? await manifest.read(bmadDir) : null;
|
|
124
|
+
const installedSkillIds = existing ? await readInstalledSkillIds(bmadDir) : new Set();
|
|
125
|
+
const currentValue = inferShimPreference({
|
|
126
|
+
requested: options.shims,
|
|
127
|
+
persisted: previousManifest?.installShims,
|
|
128
|
+
availableShims,
|
|
129
|
+
installedSkillIds,
|
|
130
|
+
existing,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (typeof options.shims === 'boolean' || options.yes) return currentValue;
|
|
134
|
+
|
|
135
|
+
return prompts.confirm({
|
|
136
|
+
message: `Install ${availableShims.length} deprecated compatibility shim skill(s)?`,
|
|
137
|
+
default: currentValue,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
113
141
|
/**
|
|
114
142
|
* Warn once for each selected module the registry marks deprecated.
|
|
115
143
|
*
|
|
@@ -302,7 +330,7 @@ class UI {
|
|
|
302
330
|
throw new Error('No valid actions available for this installation');
|
|
303
331
|
}
|
|
304
332
|
const hasQuickUpdate = choices.some((c) => c.value === 'quick-update');
|
|
305
|
-
const needsFullUpdate = !!options.customSource;
|
|
333
|
+
const needsFullUpdate = !!options.customSource || typeof options.shims === 'boolean';
|
|
306
334
|
actionType = hasQuickUpdate && !needsFullUpdate ? 'quick-update' : (choices.find((c) => c.value === 'update') || choices[0]).value;
|
|
307
335
|
await prompts.log.info(`Non-interactive mode (--yes): defaulting to ${actionType}`);
|
|
308
336
|
} else {
|
|
@@ -322,6 +350,7 @@ class UI {
|
|
|
322
350
|
actionType: 'quick-update',
|
|
323
351
|
directory: confirmedDirectory,
|
|
324
352
|
skipPrompts: options.yes || false,
|
|
353
|
+
installShims: options.shims,
|
|
325
354
|
};
|
|
326
355
|
}
|
|
327
356
|
|
|
@@ -406,6 +435,13 @@ class UI {
|
|
|
406
435
|
...options,
|
|
407
436
|
channelOptions,
|
|
408
437
|
});
|
|
438
|
+
const installShims = await this._selectShimPreference({
|
|
439
|
+
selectedModules,
|
|
440
|
+
bmadDir,
|
|
441
|
+
existing: true,
|
|
442
|
+
options,
|
|
443
|
+
channelOptions,
|
|
444
|
+
});
|
|
409
445
|
|
|
410
446
|
// Warn about --pin/--next flags that refer to modules the user didn't
|
|
411
447
|
// select, or that target bundled modules (core/bmm) where channel
|
|
@@ -432,6 +468,7 @@ class UI {
|
|
|
432
468
|
skipPrompts: options.yes || false,
|
|
433
469
|
channelOptions,
|
|
434
470
|
_preserveModules: preservedModules,
|
|
471
|
+
installShims,
|
|
435
472
|
};
|
|
436
473
|
}
|
|
437
474
|
}
|
|
@@ -487,6 +524,13 @@ class UI {
|
|
|
487
524
|
...options,
|
|
488
525
|
channelOptions,
|
|
489
526
|
});
|
|
527
|
+
const installShims = await this._selectShimPreference({
|
|
528
|
+
selectedModules,
|
|
529
|
+
bmadDir,
|
|
530
|
+
existing: false,
|
|
531
|
+
options,
|
|
532
|
+
channelOptions,
|
|
533
|
+
});
|
|
490
534
|
|
|
491
535
|
// Warn about --pin/--next flags that refer to modules the user didn't
|
|
492
536
|
// select, or that target bundled modules (core/bmm) where channel
|
|
@@ -512,6 +556,7 @@ class UI {
|
|
|
512
556
|
setOverrides,
|
|
513
557
|
skipPrompts: options.yes || false,
|
|
514
558
|
channelOptions,
|
|
559
|
+
installShims,
|
|
515
560
|
};
|
|
516
561
|
}
|
|
517
562
|
|