bmad-method 6.2.3-next.3 → 6.2.3-next.31
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/.claude-plugin/marketplace.json +0 -4
- package/README.md +8 -9
- package/README_CN.md +1 -1
- package/README_VN.md +110 -0
- package/package.json +2 -1
- package/removals.txt +17 -0
- package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +7 -4
- package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +6 -4
- package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +8 -10
- package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +96 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/agents/artifact-analyzer.md +60 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/agents/web-researcher.md +49 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/assets/prfaq-template.md +62 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/bmad-manifest.json +16 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/customer-faq.md +55 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/internal-faq.md +51 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/press-release.md +60 -0
- package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +79 -0
- package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -6
- package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-manifest.json +1 -1
- package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +8 -6
- package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +8 -6
- package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +8 -6
- package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +6 -4
- package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +6 -4
- package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +8 -9
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +8 -9
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
- package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +8 -9
- package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +8 -9
- package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +6 -4
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
- package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +9 -11
- package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +8 -14
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +10 -12
- package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +8 -12
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +11 -4
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
- package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
- package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
- package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
- package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
- package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +33 -6
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
- package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +16 -4
- package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
- package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
- package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
- package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
- package/src/bmm-skills/module-help.csv +4 -1
- package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -2
- package/src/core-skills/bmad-distillator/SKILL.md +0 -1
- package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +9 -9
- package/src/core-skills/bmad-help/SKILL.md +4 -2
- package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
- package/src/core-skills/module-help.csv +1 -0
- package/tools/installer/cli-utils.js +18 -9
- package/tools/installer/commands/install.js +0 -1
- package/tools/installer/core/existing-install.js +2 -8
- package/tools/installer/core/install-paths.js +0 -3
- package/tools/installer/core/installer.js +176 -464
- package/tools/installer/core/manifest-generator.js +4 -12
- package/tools/installer/core/manifest.js +82 -97
- package/tools/installer/ide/_config-driven.js +149 -38
- package/tools/installer/ide/platform-codes.yaml +6 -4
- package/tools/installer/ide/shared/skill-manifest.js +1 -16
- package/tools/installer/install-messages.yaml +19 -26
- package/tools/installer/modules/community-manager.js +377 -0
- package/tools/installer/modules/custom-module-manager.js +308 -0
- package/tools/installer/modules/external-manager.js +65 -49
- package/tools/installer/modules/official-modules.js +37 -65
- package/tools/installer/modules/registry-client.js +66 -0
- package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
- package/tools/installer/ui.js +340 -672
- package/tools/platform-codes.yaml +6 -0
- package/src/bmm-skills/2-plan-workflows/create-prd/data/domain-complexity.csv +0 -15
- package/src/bmm-skills/2-plan-workflows/create-prd/data/project-types.csv +0 -11
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +0 -224
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +0 -191
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +0 -209
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +0 -174
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +0 -214
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +0 -228
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +0 -217
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +0 -205
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +0 -243
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +0 -263
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +0 -209
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +0 -264
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +0 -242
- package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +0 -232
- package/src/bmm-skills/2-plan-workflows/create-prd/workflow-validate-prd.md +0 -65
- package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -59
- package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -51
- package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
- package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -53
- package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
- package/src/core-skills/bmad-init/SKILL.md +0 -100
- package/src/core-skills/bmad-init/resources/core-module.yaml +0 -25
- package/src/core-skills/bmad-init/scripts/bmad_init.py +0 -624
- package/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +0 -393
- package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
- package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
- package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
- package/src/core-skills/bmad-party-mode/workflow.md +0 -190
- package/tools/installer/core/custom-module-cache.js +0 -260
- package/tools/installer/custom-handler.js +0 -112
- package/tools/installer/modules/custom-modules.js +0 -197
- /package/src/bmm-skills/2-plan-workflows/{create-prd → bmad-edit-prd}/data/prd-purpose.md +0 -0
|
@@ -2,7 +2,6 @@ const path = require('node:path');
|
|
|
2
2
|
const fs = require('fs-extra');
|
|
3
3
|
const { Manifest } = require('./manifest');
|
|
4
4
|
const { OfficialModules } = require('../modules/official-modules');
|
|
5
|
-
const { CustomModules } = require('../modules/custom-modules');
|
|
6
5
|
const { IdeManager } = require('../ide/manager');
|
|
7
6
|
const { FileOps } = require('../file-ops');
|
|
8
7
|
const { Config } = require('./config');
|
|
@@ -19,13 +18,50 @@ class Installer {
|
|
|
19
18
|
constructor() {
|
|
20
19
|
this.externalModuleManager = new ExternalModuleManager();
|
|
21
20
|
this.manifest = new Manifest();
|
|
22
|
-
this.customModules = new CustomModules();
|
|
23
21
|
this.ideManager = new IdeManager();
|
|
24
22
|
this.fileOps = new FileOps();
|
|
25
23
|
this.installedFiles = new Set(); // Track all installed files
|
|
26
24
|
this.bmadFolderName = BMAD_FOLDER_NAME;
|
|
27
25
|
}
|
|
28
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Read the module version from .claude-plugin/marketplace.json
|
|
29
|
+
* Walks up from sourcePath looking for .claude-plugin/marketplace.json
|
|
30
|
+
* @param {string} sourcePath - Module source directory
|
|
31
|
+
* @returns {string} Version string or empty string
|
|
32
|
+
*/
|
|
33
|
+
async _getMarketplaceVersion(sourcePath) {
|
|
34
|
+
let dir = sourcePath;
|
|
35
|
+
for (let i = 0; i < 5; i++) {
|
|
36
|
+
const marketplacePath = path.join(dir, '.claude-plugin', 'marketplace.json');
|
|
37
|
+
if (await fs.pathExists(marketplacePath)) {
|
|
38
|
+
try {
|
|
39
|
+
const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
|
|
40
|
+
return this._extractMarketplaceVersion(data);
|
|
41
|
+
} catch {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const parent = path.dirname(dir);
|
|
46
|
+
if (parent === dir) break;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract the highest version from marketplace.json plugins array
|
|
54
|
+
*/
|
|
55
|
+
_extractMarketplaceVersion(data) {
|
|
56
|
+
const plugins = data?.plugins;
|
|
57
|
+
if (!Array.isArray(plugins) || plugins.length === 0) return '';
|
|
58
|
+
let best = '';
|
|
59
|
+
for (const p of plugins) {
|
|
60
|
+
if (p.version && (!best || p.version > best)) best = p.version;
|
|
61
|
+
}
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
|
|
29
65
|
/**
|
|
30
66
|
* Main installation method
|
|
31
67
|
* @param {Object} config - Installation configuration
|
|
@@ -42,8 +78,6 @@ class Installer {
|
|
|
42
78
|
const officialModules = await OfficialModules.build(config, paths);
|
|
43
79
|
const existingInstall = await ExistingInstall.detect(paths.bmadDir);
|
|
44
80
|
|
|
45
|
-
await this.customModules.discoverPaths(originalConfig, paths);
|
|
46
|
-
|
|
47
81
|
if (existingInstall.installed) {
|
|
48
82
|
await this._removeDeselectedModules(existingInstall, config, paths);
|
|
49
83
|
updateState = await this._prepareUpdateState(paths, config, existingInstall, officialModules);
|
|
@@ -52,20 +86,46 @@ class Installer {
|
|
|
52
86
|
|
|
53
87
|
await this._validateIdeSelection(config);
|
|
54
88
|
|
|
89
|
+
// Capture pre-install module versions for from→to display
|
|
90
|
+
const preInstallVersions = new Map();
|
|
91
|
+
if (existingInstall.installed) {
|
|
92
|
+
const existingModules = await this.manifest.getAllModuleVersions(paths.bmadDir);
|
|
93
|
+
for (const mod of existingModules) {
|
|
94
|
+
if (mod.name && mod.version) {
|
|
95
|
+
preInstallVersions.set(mod.name, mod.version);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
55
100
|
// Results collector for consolidated summary
|
|
56
101
|
const results = [];
|
|
57
|
-
const addResult = (step, status, detail = '') => results.push({ step, status, detail });
|
|
102
|
+
const addResult = (step, status, detail = '', meta = {}) => results.push({ step, status, detail, ...meta });
|
|
103
|
+
|
|
104
|
+
// Capture previously installed skill IDs before they get overwritten
|
|
105
|
+
const previousSkillIds = new Set();
|
|
106
|
+
const prevCsvPath = path.join(paths.bmadDir, '_config', 'skill-manifest.csv');
|
|
107
|
+
if (await fs.pathExists(prevCsvPath)) {
|
|
108
|
+
try {
|
|
109
|
+
const csvParse = require('csv-parse/sync');
|
|
110
|
+
const content = await fs.readFile(prevCsvPath, 'utf8');
|
|
111
|
+
const records = csvParse.parse(content, { columns: true, skip_empty_lines: true });
|
|
112
|
+
for (const r of records) {
|
|
113
|
+
if (r.canonicalId) previousSkillIds.add(r.canonicalId);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
await prompts.log.warn(`Failed to parse skill-manifest.csv: ${error.message}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
58
119
|
|
|
59
|
-
|
|
120
|
+
const allModules = config.modules || [];
|
|
60
121
|
|
|
61
|
-
|
|
62
|
-
const customModuleIds = new Set(this.customModules.paths.keys());
|
|
63
|
-
const officialModuleIds = (config.modules || []).filter((m) => !customModuleIds.has(m));
|
|
64
|
-
const allModules = [...officialModuleIds, ...[...customModuleIds].filter((id) => !officialModuleIds.includes(id))];
|
|
122
|
+
await this._installAndConfigure(config, originalConfig, paths, allModules, allModules, addResult, officialModules);
|
|
65
123
|
|
|
66
|
-
await this.
|
|
124
|
+
await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
|
|
67
125
|
|
|
68
|
-
|
|
126
|
+
// Skills are now in IDE directories — remove redundant copies from _bmad/.
|
|
127
|
+
// Also cleans up skill dirs left by older installer versions.
|
|
128
|
+
await this._cleanupSkillDirs(paths.bmadDir);
|
|
69
129
|
|
|
70
130
|
const restoreResult = await this._restoreUserFiles(paths, updateState);
|
|
71
131
|
|
|
@@ -76,6 +136,7 @@ class Installer {
|
|
|
76
136
|
ides: config.ides,
|
|
77
137
|
customFiles: restoreResult.customFiles.length > 0 ? restoreResult.customFiles : undefined,
|
|
78
138
|
modifiedFiles: restoreResult.modifiedFiles.length > 0 ? restoreResult.modifiedFiles : undefined,
|
|
139
|
+
preInstallVersions,
|
|
79
140
|
});
|
|
80
141
|
|
|
81
142
|
return {
|
|
@@ -172,26 +233,6 @@ class Installer {
|
|
|
172
233
|
}
|
|
173
234
|
}
|
|
174
235
|
|
|
175
|
-
/**
|
|
176
|
-
* Cache custom modules into the local cache directory.
|
|
177
|
-
* Updates this.customModules.paths in place with cached locations.
|
|
178
|
-
*/
|
|
179
|
-
async _cacheCustomModules(paths, addResult) {
|
|
180
|
-
if (!this.customModules.paths || this.customModules.paths.size === 0) return;
|
|
181
|
-
|
|
182
|
-
const { CustomModuleCache } = require('./custom-module-cache');
|
|
183
|
-
const customCache = new CustomModuleCache(paths.bmadDir);
|
|
184
|
-
|
|
185
|
-
for (const [moduleId, sourcePath] of this.customModules.paths) {
|
|
186
|
-
const cachedInfo = await customCache.cacheModule(moduleId, sourcePath, {
|
|
187
|
-
sourcePath: sourcePath,
|
|
188
|
-
});
|
|
189
|
-
this.customModules.paths.set(moduleId, cachedInfo.cachePath);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
addResult('Custom modules cached', 'ok');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
236
|
/**
|
|
196
237
|
* Install modules, create directories, generate configs and manifests.
|
|
197
238
|
*/
|
|
@@ -214,11 +255,6 @@ class Installer {
|
|
|
214
255
|
installedModuleNames,
|
|
215
256
|
});
|
|
216
257
|
|
|
217
|
-
await this._installCustomModules(config, paths, addResult, officialModules, {
|
|
218
|
-
message,
|
|
219
|
-
installedModuleNames,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
258
|
return `${allModules.length} module(s) ${isQuickUpdate ? 'updated' : 'installed'}`;
|
|
223
259
|
},
|
|
224
260
|
});
|
|
@@ -321,7 +357,7 @@ class Installer {
|
|
|
321
357
|
/**
|
|
322
358
|
* Set up IDE integrations for each selected IDE.
|
|
323
359
|
*/
|
|
324
|
-
async _setupIdes(config, allModules, paths, addResult) {
|
|
360
|
+
async _setupIdes(config, allModules, paths, addResult, previousSkillIds = new Set()) {
|
|
325
361
|
if (config.skipIde || !config.ides || config.ides.length === 0) return;
|
|
326
362
|
|
|
327
363
|
await this.ideManager.ensureInitialized();
|
|
@@ -336,6 +372,7 @@ class Installer {
|
|
|
336
372
|
const setupResult = await this.ideManager.setup(ide, paths.projectRoot, paths.bmadDir, {
|
|
337
373
|
selectedModules: allModules || [],
|
|
338
374
|
verbose: config.verbose,
|
|
375
|
+
previousSkillIds,
|
|
339
376
|
});
|
|
340
377
|
|
|
341
378
|
if (setupResult.success) {
|
|
@@ -346,6 +383,33 @@ class Installer {
|
|
|
346
383
|
}
|
|
347
384
|
}
|
|
348
385
|
|
|
386
|
+
/**
|
|
387
|
+
* Remove skill directories from _bmad/ after IDE installation.
|
|
388
|
+
* Skills are self-contained in IDE directories, so _bmad/ only needs
|
|
389
|
+
* module-level files (config.yaml, _config/, etc.).
|
|
390
|
+
* Also cleans up skill dirs left by older installer versions.
|
|
391
|
+
* @param {string} bmadDir - BMAD installation directory
|
|
392
|
+
*/
|
|
393
|
+
async _cleanupSkillDirs(bmadDir) {
|
|
394
|
+
const csv = require('csv-parse/sync');
|
|
395
|
+
const csvPath = path.join(bmadDir, '_config', 'skill-manifest.csv');
|
|
396
|
+
if (!(await fs.pathExists(csvPath))) return;
|
|
397
|
+
|
|
398
|
+
const csvContent = await fs.readFile(csvPath, 'utf8');
|
|
399
|
+
const records = csv.parse(csvContent, { columns: true, skip_empty_lines: true });
|
|
400
|
+
const bmadFolderName = path.basename(bmadDir);
|
|
401
|
+
const bmadPrefix = bmadFolderName + '/';
|
|
402
|
+
|
|
403
|
+
for (const record of records) {
|
|
404
|
+
if (!record.path) continue;
|
|
405
|
+
const relativePath = record.path.startsWith(bmadPrefix) ? record.path.slice(bmadPrefix.length) : record.path;
|
|
406
|
+
const sourceDir = path.dirname(path.join(bmadDir, relativePath));
|
|
407
|
+
if (await fs.pathExists(sourceDir)) {
|
|
408
|
+
await fs.remove(sourceDir);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
349
413
|
/**
|
|
350
414
|
* Restore custom and modified files that were backed up before the update.
|
|
351
415
|
* No-op for fresh installs (updateState is null).
|
|
@@ -417,48 +481,7 @@ class Installer {
|
|
|
417
481
|
}
|
|
418
482
|
|
|
419
483
|
/**
|
|
420
|
-
*
|
|
421
|
-
* that aren't already known from the manifest or external module list.
|
|
422
|
-
* @param {Object} paths - InstallPaths instance
|
|
423
|
-
*/
|
|
424
|
-
async _scanCachedCustomModules(paths) {
|
|
425
|
-
const cacheDir = paths.customCacheDir;
|
|
426
|
-
if (!(await fs.pathExists(cacheDir))) {
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
431
|
-
|
|
432
|
-
for (const cachedModule of cachedModules) {
|
|
433
|
-
const moduleId = cachedModule.name;
|
|
434
|
-
const cachedPath = path.join(cacheDir, moduleId);
|
|
435
|
-
|
|
436
|
-
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
437
|
-
if (!(await fs.pathExists(cachedPath)) || !cachedModule.isDirectory()) {
|
|
438
|
-
continue;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
// Skip if we already have this module from manifest
|
|
442
|
-
if (this.customModules.paths.has(moduleId)) {
|
|
443
|
-
continue;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// Check if this is an external official module - skip cache for those
|
|
447
|
-
const isExternal = await this.externalModuleManager.hasModule(moduleId);
|
|
448
|
-
if (isExternal) {
|
|
449
|
-
continue;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Check if this is actually a custom module (has module.yaml)
|
|
453
|
-
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
454
|
-
if (await fs.pathExists(moduleYamlPath)) {
|
|
455
|
-
this.customModules.paths.set(moduleId, cachedPath);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Common update preparation: detect files, preserve core config, scan cache, back up.
|
|
484
|
+
* Common update preparation: detect files, preserve core config, back up.
|
|
462
485
|
* @param {Object} paths - InstallPaths instance
|
|
463
486
|
* @param {Object} config - Clean config (may have coreConfig updated)
|
|
464
487
|
* @param {Object} existingInstall - Detection result
|
|
@@ -486,8 +509,6 @@ class Installer {
|
|
|
486
509
|
}
|
|
487
510
|
}
|
|
488
511
|
|
|
489
|
-
await this._scanCachedCustomModules(paths);
|
|
490
|
-
|
|
491
512
|
const backupDirs = await this._backupUserFiles(paths, customFiles, modifiedFiles);
|
|
492
513
|
|
|
493
514
|
return {
|
|
@@ -556,7 +577,7 @@ class Installer {
|
|
|
556
577
|
message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
|
|
557
578
|
|
|
558
579
|
const moduleConfig = officialModules.moduleConfigs[moduleName] || {};
|
|
559
|
-
await officialModules.install(
|
|
580
|
+
const installResult = await officialModules.install(
|
|
560
581
|
moduleName,
|
|
561
582
|
paths.bmadDir,
|
|
562
583
|
(filePath) => {
|
|
@@ -570,35 +591,12 @@ class Installer {
|
|
|
570
591
|
},
|
|
571
592
|
);
|
|
572
593
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
* Source paths come from this.customModules.paths (populated by discoverPaths).
|
|
580
|
-
*/
|
|
581
|
-
async _installCustomModules(config, paths, addResult, officialModules, ctx) {
|
|
582
|
-
const { message, installedModuleNames } = ctx;
|
|
583
|
-
const isQuickUpdate = config.isQuickUpdate();
|
|
584
|
-
|
|
585
|
-
for (const [moduleName, sourcePath] of this.customModules.paths) {
|
|
586
|
-
if (installedModuleNames.has(moduleName)) continue;
|
|
587
|
-
installedModuleNames.add(moduleName);
|
|
588
|
-
|
|
589
|
-
message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
|
|
590
|
-
|
|
591
|
-
const collectedModuleConfig = officialModules.moduleConfigs[moduleName] || {};
|
|
592
|
-
const result = await this.customModules.install(moduleName, paths.bmadDir, (filePath) => this.installedFiles.add(filePath), {
|
|
593
|
-
moduleConfig: collectedModuleConfig,
|
|
594
|
-
});
|
|
595
|
-
|
|
596
|
-
// Generate runtime config.yaml with merged values
|
|
597
|
-
await this.generateModuleConfigs(paths.bmadDir, {
|
|
598
|
-
[moduleName]: { ...config.coreConfig, ...result.moduleConfig, ...collectedModuleConfig },
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
|
|
594
|
+
// Get display name from source module.yaml; version from marketplace.json
|
|
595
|
+
const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
|
|
596
|
+
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
|
|
597
|
+
const displayName = moduleInfo?.name || moduleName;
|
|
598
|
+
const version = sourcePath ? await this._getMarketplaceVersion(sourcePath) : '';
|
|
599
|
+
addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
|
|
602
600
|
}
|
|
603
601
|
}
|
|
604
602
|
|
|
@@ -971,6 +969,14 @@ class Installer {
|
|
|
971
969
|
outputs,
|
|
972
970
|
] = columns;
|
|
973
971
|
|
|
972
|
+
// Pass through _meta rows as-is (module metadata, not a skill)
|
|
973
|
+
if (phase === '_meta') {
|
|
974
|
+
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
|
975
|
+
const metaRow = [finalModule, '_meta', '', '', '', '', '', 'false', '', '', '', '', '', '', outputLocation || '', ''];
|
|
976
|
+
allRows.push(metaRow.map((c) => this.escapeCSVField(c)).join(','));
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
|
|
974
980
|
// If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
|
|
975
981
|
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
|
976
982
|
|
|
@@ -1062,23 +1068,10 @@ class Installer {
|
|
|
1062
1068
|
const selectedIdes = new Set((context.ides || []).map((ide) => String(ide).toLowerCase()));
|
|
1063
1069
|
|
|
1064
1070
|
// Build step lines with status indicators
|
|
1071
|
+
const preVersions = context.preInstallVersions || new Map();
|
|
1065
1072
|
const lines = [];
|
|
1066
1073
|
for (const r of results) {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
if (r.status !== 'ok') {
|
|
1070
|
-
stepLabel = r.step;
|
|
1071
|
-
} else if (r.step === 'Core') {
|
|
1072
|
-
stepLabel = 'BMAD';
|
|
1073
|
-
} else if (r.step.startsWith('Module: ')) {
|
|
1074
|
-
stepLabel = r.step;
|
|
1075
|
-
} else if (selectedIdes.has(String(r.step).toLowerCase())) {
|
|
1076
|
-
stepLabel = r.step;
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
if (!stepLabel) {
|
|
1080
|
-
continue;
|
|
1081
|
-
}
|
|
1074
|
+
const stepLabel = r.step;
|
|
1082
1075
|
|
|
1083
1076
|
let icon;
|
|
1084
1077
|
if (r.status === 'ok') {
|
|
@@ -1088,18 +1081,32 @@ class Installer {
|
|
|
1088
1081
|
} else {
|
|
1089
1082
|
icon = color.red('\u2717');
|
|
1090
1083
|
}
|
|
1091
|
-
|
|
1084
|
+
|
|
1085
|
+
// Build version detail for module results
|
|
1086
|
+
let detail = '';
|
|
1087
|
+
if (r.moduleCode && r.newVersion) {
|
|
1088
|
+
const oldVersion = preVersions.get(r.moduleCode);
|
|
1089
|
+
if (oldVersion && oldVersion === r.newVersion) {
|
|
1090
|
+
detail = ` (v${r.newVersion}, no change)`;
|
|
1091
|
+
} else if (oldVersion) {
|
|
1092
|
+
detail = ` (v${oldVersion} → v${r.newVersion})`;
|
|
1093
|
+
} else {
|
|
1094
|
+
detail = ` (v${r.newVersion}, installed)`;
|
|
1095
|
+
}
|
|
1096
|
+
} else if (r.detail) {
|
|
1097
|
+
detail = ` (${r.detail})`;
|
|
1098
|
+
}
|
|
1092
1099
|
lines.push(` ${icon} ${stepLabel}${detail}`);
|
|
1093
1100
|
}
|
|
1094
1101
|
|
|
1095
1102
|
if ((context.ides || []).length === 0) {
|
|
1096
|
-
lines.push(` ${color.green('\u2713')} No IDE selected
|
|
1103
|
+
lines.push(` ${color.green('\u2713')} No IDE selected (installed in _bmad only)`);
|
|
1097
1104
|
}
|
|
1098
1105
|
|
|
1099
1106
|
// Context and warnings
|
|
1100
1107
|
lines.push('');
|
|
1101
1108
|
if (context.bmadDir) {
|
|
1102
|
-
lines.push(` Installed to: ${
|
|
1109
|
+
lines.push(` Installed to: ${context.bmadDir}`);
|
|
1103
1110
|
}
|
|
1104
1111
|
if (context.customFiles && context.customFiles.length > 0) {
|
|
1105
1112
|
lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
|
|
@@ -1111,17 +1118,18 @@ class Installer {
|
|
|
1111
1118
|
// Next steps
|
|
1112
1119
|
lines.push(
|
|
1113
1120
|
'',
|
|
1114
|
-
'
|
|
1115
|
-
`
|
|
1116
|
-
`
|
|
1117
|
-
|
|
1118
|
-
`
|
|
1121
|
+
' Get started:',
|
|
1122
|
+
` 1. Launch your AI agent from your project folder`,
|
|
1123
|
+
` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`,
|
|
1124
|
+
'',
|
|
1125
|
+
` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`,
|
|
1126
|
+
` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`,
|
|
1119
1127
|
);
|
|
1120
|
-
if (context.ides && context.ides.length > 0) {
|
|
1121
|
-
lines.push(` Invoke the ${color.cyan('bmad-help')} skill in your IDE Agent to get started`);
|
|
1122
|
-
}
|
|
1123
1128
|
|
|
1124
|
-
await prompts.
|
|
1129
|
+
await prompts.box(lines.join('\n'), 'BMAD is ready to use!', {
|
|
1130
|
+
rounded: true,
|
|
1131
|
+
formatBorder: color.green,
|
|
1132
|
+
});
|
|
1125
1133
|
}
|
|
1126
1134
|
|
|
1127
1135
|
/**
|
|
@@ -1144,63 +1152,9 @@ class Installer {
|
|
|
1144
1152
|
const configuredIdes = existingInstall.ides;
|
|
1145
1153
|
const projectRoot = path.dirname(bmadDir);
|
|
1146
1154
|
|
|
1147
|
-
// Get custom module sources: first from --custom-content (re-cache from source), then from cache
|
|
1148
|
-
const customModuleSources = new Map();
|
|
1149
|
-
if (config.customContent?.sources?.length > 0) {
|
|
1150
|
-
for (const source of config.customContent.sources) {
|
|
1151
|
-
if (source.id && source.path && (await fs.pathExists(source.path))) {
|
|
1152
|
-
customModuleSources.set(source.id, {
|
|
1153
|
-
id: source.id,
|
|
1154
|
-
name: source.name || source.id,
|
|
1155
|
-
sourcePath: source.path,
|
|
1156
|
-
cached: false, // From CLI, will be re-cached
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
const cacheDir = path.join(bmadDir, '_config', 'custom');
|
|
1162
|
-
if (await fs.pathExists(cacheDir)) {
|
|
1163
|
-
const cachedModules = await fs.readdir(cacheDir, { withFileTypes: true });
|
|
1164
|
-
|
|
1165
|
-
for (const cachedModule of cachedModules) {
|
|
1166
|
-
const moduleId = cachedModule.name;
|
|
1167
|
-
const cachedPath = path.join(cacheDir, moduleId);
|
|
1168
|
-
|
|
1169
|
-
// Skip if path doesn't exist (broken symlink, deleted dir) - avoids lstat ENOENT
|
|
1170
|
-
if (!(await fs.pathExists(cachedPath))) {
|
|
1171
|
-
continue;
|
|
1172
|
-
}
|
|
1173
|
-
if (!cachedModule.isDirectory()) {
|
|
1174
|
-
continue;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
// Skip if we already have this module from manifest
|
|
1178
|
-
if (customModuleSources.has(moduleId)) {
|
|
1179
|
-
continue;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
// Check if this is an external official module - skip cache for those
|
|
1183
|
-
const isExternal = await this.externalModuleManager.hasModule(moduleId);
|
|
1184
|
-
if (isExternal) {
|
|
1185
|
-
continue;
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// Check if this is actually a custom module (has module.yaml)
|
|
1189
|
-
const moduleYamlPath = path.join(cachedPath, 'module.yaml');
|
|
1190
|
-
if (await fs.pathExists(moduleYamlPath)) {
|
|
1191
|
-
customModuleSources.set(moduleId, {
|
|
1192
|
-
id: moduleId,
|
|
1193
|
-
name: moduleId,
|
|
1194
|
-
sourcePath: cachedPath,
|
|
1195
|
-
cached: true,
|
|
1196
|
-
});
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
1155
|
// Get available modules (what we have source for)
|
|
1202
1156
|
const availableModulesData = await new OfficialModules().listAvailable();
|
|
1203
|
-
const availableModules = [...availableModulesData.modules
|
|
1157
|
+
const availableModules = [...availableModulesData.modules];
|
|
1204
1158
|
|
|
1205
1159
|
// Add external official modules to available modules
|
|
1206
1160
|
const externalModules = await this.externalModuleManager.listAvailable();
|
|
@@ -1215,52 +1169,44 @@ class Installer {
|
|
|
1215
1169
|
}
|
|
1216
1170
|
}
|
|
1217
1171
|
|
|
1218
|
-
// Add
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1172
|
+
// Add installed community modules to available modules
|
|
1173
|
+
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
1174
|
+
const communityMgr = new CommunityModuleManager();
|
|
1175
|
+
const communityModules = await communityMgr.listAll();
|
|
1176
|
+
for (const communityModule of communityModules) {
|
|
1177
|
+
if (installedModules.includes(communityModule.code) && !availableModules.some((m) => m.id === communityModule.code)) {
|
|
1222
1178
|
availableModules.push({
|
|
1223
|
-
id:
|
|
1224
|
-
name:
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
fromManifest: true,
|
|
1179
|
+
id: communityModule.code,
|
|
1180
|
+
name: communityModule.displayName,
|
|
1181
|
+
isExternal: true,
|
|
1182
|
+
fromCommunity: true,
|
|
1228
1183
|
});
|
|
1229
1184
|
}
|
|
1230
1185
|
}
|
|
1231
1186
|
|
|
1232
|
-
//
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
}));
|
|
1187
|
+
// Add installed custom modules to available modules
|
|
1188
|
+
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
1189
|
+
const customMgr = new CustomModuleManager();
|
|
1190
|
+
for (const moduleId of installedModules) {
|
|
1191
|
+
if (!availableModules.some((m) => m.id === moduleId)) {
|
|
1192
|
+
const customSource = await customMgr.findModuleSourceByCode(moduleId);
|
|
1193
|
+
if (customSource) {
|
|
1194
|
+
availableModules.push({
|
|
1195
|
+
id: moduleId,
|
|
1196
|
+
name: moduleId,
|
|
1197
|
+
isExternal: true,
|
|
1198
|
+
fromCustom: true,
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1249
1203
|
|
|
1250
|
-
const
|
|
1251
|
-
const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
|
|
1204
|
+
const availableModuleIds = new Set(availableModules.map((m) => m.id));
|
|
1252
1205
|
|
|
1253
1206
|
// Only update modules that are BOTH installed AND available (we have source for)
|
|
1254
1207
|
const modulesToUpdate = installedModules.filter((id) => availableModuleIds.has(id));
|
|
1255
1208
|
const skippedModules = installedModules.filter((id) => !availableModuleIds.has(id));
|
|
1256
1209
|
|
|
1257
|
-
// Add custom modules that were kept without sources to the skipped modules
|
|
1258
|
-
for (const keptModule of keptModulesWithoutSources) {
|
|
1259
|
-
if (!skippedModules.includes(keptModule)) {
|
|
1260
|
-
skippedModules.push(keptModule);
|
|
1261
|
-
}
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
1210
|
if (skippedModules.length > 0) {
|
|
1265
1211
|
await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
|
|
1266
1212
|
}
|
|
@@ -1278,6 +1224,7 @@ class Installer {
|
|
|
1278
1224
|
}
|
|
1279
1225
|
|
|
1280
1226
|
for (const moduleName of modulesToUpdate) {
|
|
1227
|
+
if (moduleName === 'core') continue; // Already collected above
|
|
1281
1228
|
const modulePrompted = await quickModules.collectModuleConfigQuick(moduleName, projectDir, true);
|
|
1282
1229
|
if (modulePrompted) {
|
|
1283
1230
|
promptedForNewFields = true;
|
|
@@ -1304,9 +1251,7 @@ class Installer {
|
|
|
1304
1251
|
actionType: 'install',
|
|
1305
1252
|
_quickUpdate: true,
|
|
1306
1253
|
_preserveModules: skippedModules,
|
|
1307
|
-
_customModuleSources: customModuleSources,
|
|
1308
1254
|
_existingModules: installedModules,
|
|
1309
|
-
customContent: config.customContent,
|
|
1310
1255
|
};
|
|
1311
1256
|
|
|
1312
1257
|
await this.install(installConfig);
|
|
@@ -1441,239 +1386,6 @@ class Installer {
|
|
|
1441
1386
|
return this._readOutputFolder(bmadDir);
|
|
1442
1387
|
}
|
|
1443
1388
|
|
|
1444
|
-
/**
|
|
1445
|
-
* Handle missing custom module sources interactively
|
|
1446
|
-
* @param {Map} customModuleSources - Map of custom module ID to info
|
|
1447
|
-
* @param {string} bmadDir - BMAD directory
|
|
1448
|
-
* @param {string} projectRoot - Project root directory
|
|
1449
|
-
* @param {string} operation - Current operation ('update', 'compile', etc.)
|
|
1450
|
-
* @param {Array} installedModules - Array of installed module IDs (will be modified)
|
|
1451
|
-
* @param {boolean} [skipPrompts=false] - Skip interactive prompts and keep all modules with missing sources
|
|
1452
|
-
* @returns {Object} Object with validCustomModules array and keptModulesWithoutSources array
|
|
1453
|
-
*/
|
|
1454
|
-
async handleMissingCustomSources(customModuleSources, bmadDir, projectRoot, operation, installedModules, skipPrompts = false) {
|
|
1455
|
-
const validCustomModules = [];
|
|
1456
|
-
const keptModulesWithoutSources = []; // Track modules kept without sources
|
|
1457
|
-
const customModulesWithMissingSources = [];
|
|
1458
|
-
|
|
1459
|
-
// Check which sources exist
|
|
1460
|
-
for (const [moduleId, customInfo] of customModuleSources) {
|
|
1461
|
-
if (await fs.pathExists(customInfo.sourcePath)) {
|
|
1462
|
-
validCustomModules.push({
|
|
1463
|
-
id: moduleId,
|
|
1464
|
-
name: customInfo.name,
|
|
1465
|
-
path: customInfo.sourcePath,
|
|
1466
|
-
info: customInfo,
|
|
1467
|
-
});
|
|
1468
|
-
} else {
|
|
1469
|
-
// For cached modules that are missing, we just skip them without prompting
|
|
1470
|
-
if (customInfo.cached) {
|
|
1471
|
-
// Skip cached modules without prompting
|
|
1472
|
-
keptModulesWithoutSources.push({
|
|
1473
|
-
id: moduleId,
|
|
1474
|
-
name: customInfo.name,
|
|
1475
|
-
cached: true,
|
|
1476
|
-
});
|
|
1477
|
-
} else {
|
|
1478
|
-
customModulesWithMissingSources.push({
|
|
1479
|
-
id: moduleId,
|
|
1480
|
-
name: customInfo.name,
|
|
1481
|
-
sourcePath: customInfo.sourcePath,
|
|
1482
|
-
relativePath: customInfo.relativePath,
|
|
1483
|
-
info: customInfo,
|
|
1484
|
-
});
|
|
1485
|
-
}
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
// If no missing sources, return immediately
|
|
1490
|
-
if (customModulesWithMissingSources.length === 0) {
|
|
1491
|
-
return {
|
|
1492
|
-
validCustomModules,
|
|
1493
|
-
keptModulesWithoutSources: [],
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
// Non-interactive mode: keep all modules with missing sources
|
|
1498
|
-
if (skipPrompts) {
|
|
1499
|
-
for (const missing of customModulesWithMissingSources) {
|
|
1500
|
-
keptModulesWithoutSources.push(missing.id);
|
|
1501
|
-
}
|
|
1502
|
-
return { validCustomModules, keptModulesWithoutSources };
|
|
1503
|
-
}
|
|
1504
|
-
|
|
1505
|
-
await prompts.log.warn(`Found ${customModulesWithMissingSources.length} custom module(s) with missing sources:`);
|
|
1506
|
-
|
|
1507
|
-
let keptCount = 0;
|
|
1508
|
-
let updatedCount = 0;
|
|
1509
|
-
let removedCount = 0;
|
|
1510
|
-
|
|
1511
|
-
for (const missing of customModulesWithMissingSources) {
|
|
1512
|
-
await prompts.log.message(
|
|
1513
|
-
`${missing.name} (${missing.id})\n Original source: ${missing.relativePath}\n Full path: ${missing.sourcePath}`,
|
|
1514
|
-
);
|
|
1515
|
-
|
|
1516
|
-
const choices = [
|
|
1517
|
-
{
|
|
1518
|
-
name: 'Keep installed (will not be processed)',
|
|
1519
|
-
value: 'keep',
|
|
1520
|
-
hint: 'Keep',
|
|
1521
|
-
},
|
|
1522
|
-
{
|
|
1523
|
-
name: 'Specify new source location',
|
|
1524
|
-
value: 'update',
|
|
1525
|
-
hint: 'Update',
|
|
1526
|
-
},
|
|
1527
|
-
];
|
|
1528
|
-
|
|
1529
|
-
// Only add remove option if not just compiling agents
|
|
1530
|
-
if (operation !== 'compile-agents') {
|
|
1531
|
-
choices.push({
|
|
1532
|
-
name: '⚠️ REMOVE module completely (destructive!)',
|
|
1533
|
-
value: 'remove',
|
|
1534
|
-
hint: 'Remove',
|
|
1535
|
-
});
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
const action = await prompts.select({
|
|
1539
|
-
message: `How would you like to handle "${missing.name}"?`,
|
|
1540
|
-
choices,
|
|
1541
|
-
});
|
|
1542
|
-
|
|
1543
|
-
switch (action) {
|
|
1544
|
-
case 'update': {
|
|
1545
|
-
// Use sync validation because @clack/prompts doesn't support async validate
|
|
1546
|
-
const newSourcePath = await prompts.text({
|
|
1547
|
-
message: 'Enter the new path to the custom module:',
|
|
1548
|
-
default: missing.sourcePath,
|
|
1549
|
-
validate: (input) => {
|
|
1550
|
-
if (!input || input.trim() === '') {
|
|
1551
|
-
return 'Please enter a path';
|
|
1552
|
-
}
|
|
1553
|
-
const expandedPath = path.resolve(input.trim());
|
|
1554
|
-
if (!fs.pathExistsSync(expandedPath)) {
|
|
1555
|
-
return 'Path does not exist';
|
|
1556
|
-
}
|
|
1557
|
-
// Check if it looks like a valid module
|
|
1558
|
-
const moduleYamlPath = path.join(expandedPath, 'module.yaml');
|
|
1559
|
-
const agentsPath = path.join(expandedPath, 'agents');
|
|
1560
|
-
const workflowsPath = path.join(expandedPath, 'workflows');
|
|
1561
|
-
|
|
1562
|
-
if (!fs.pathExistsSync(moduleYamlPath) && !fs.pathExistsSync(agentsPath) && !fs.pathExistsSync(workflowsPath)) {
|
|
1563
|
-
return 'Path does not appear to contain a valid custom module';
|
|
1564
|
-
}
|
|
1565
|
-
return; // clack expects undefined for valid input
|
|
1566
|
-
},
|
|
1567
|
-
});
|
|
1568
|
-
|
|
1569
|
-
// Defensive: handleCancel should have exited, but guard against symbol propagation
|
|
1570
|
-
if (typeof newSourcePath !== 'string') {
|
|
1571
|
-
keptCount++;
|
|
1572
|
-
keptModulesWithoutSources.push(missing.id);
|
|
1573
|
-
continue;
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
// Update the source in manifest
|
|
1577
|
-
const resolvedPath = path.resolve(newSourcePath.trim());
|
|
1578
|
-
missing.info.sourcePath = resolvedPath;
|
|
1579
|
-
// Remove relativePath - we only store absolute sourcePath now
|
|
1580
|
-
delete missing.info.relativePath;
|
|
1581
|
-
await this.manifest.addCustomModule(bmadDir, missing.info);
|
|
1582
|
-
|
|
1583
|
-
validCustomModules.push({
|
|
1584
|
-
id: missing.id,
|
|
1585
|
-
name: missing.name,
|
|
1586
|
-
path: resolvedPath,
|
|
1587
|
-
info: missing.info,
|
|
1588
|
-
});
|
|
1589
|
-
|
|
1590
|
-
updatedCount++;
|
|
1591
|
-
await prompts.log.success('Updated source location');
|
|
1592
|
-
|
|
1593
|
-
break;
|
|
1594
|
-
}
|
|
1595
|
-
case 'remove': {
|
|
1596
|
-
// Extra confirmation for destructive remove
|
|
1597
|
-
await prompts.log.error(
|
|
1598
|
-
`WARNING: This will PERMANENTLY DELETE "${missing.name}" and all its files!\n Module location: ${path.join(bmadDir, missing.id)}`,
|
|
1599
|
-
);
|
|
1600
|
-
|
|
1601
|
-
const confirmDelete = await prompts.confirm({
|
|
1602
|
-
message: 'Are you absolutely sure you want to delete this module?',
|
|
1603
|
-
default: false,
|
|
1604
|
-
});
|
|
1605
|
-
|
|
1606
|
-
if (confirmDelete) {
|
|
1607
|
-
const typedConfirm = await prompts.text({
|
|
1608
|
-
message: 'Type "DELETE" to confirm permanent deletion:',
|
|
1609
|
-
validate: (input) => {
|
|
1610
|
-
if (input !== 'DELETE') {
|
|
1611
|
-
return 'You must type "DELETE" exactly to proceed';
|
|
1612
|
-
}
|
|
1613
|
-
return; // clack expects undefined for valid input
|
|
1614
|
-
},
|
|
1615
|
-
});
|
|
1616
|
-
|
|
1617
|
-
if (typedConfirm === 'DELETE') {
|
|
1618
|
-
// Remove the module from filesystem and manifest
|
|
1619
|
-
const modulePath = path.join(bmadDir, missing.id);
|
|
1620
|
-
if (await fs.pathExists(modulePath)) {
|
|
1621
|
-
const fsExtra = require('fs-extra');
|
|
1622
|
-
await fsExtra.remove(modulePath);
|
|
1623
|
-
await prompts.log.warn(`Deleted module directory: ${path.relative(projectRoot, modulePath)}`);
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
await this.manifest.removeModule(bmadDir, missing.id);
|
|
1627
|
-
await this.manifest.removeCustomModule(bmadDir, missing.id);
|
|
1628
|
-
await prompts.log.warn('Removed from manifest');
|
|
1629
|
-
|
|
1630
|
-
// Also remove from installedModules list
|
|
1631
|
-
if (installedModules && installedModules.includes(missing.id)) {
|
|
1632
|
-
const index = installedModules.indexOf(missing.id);
|
|
1633
|
-
if (index !== -1) {
|
|
1634
|
-
installedModules.splice(index, 1);
|
|
1635
|
-
}
|
|
1636
|
-
}
|
|
1637
|
-
|
|
1638
|
-
removedCount++;
|
|
1639
|
-
await prompts.log.error(`"${missing.name}" has been permanently removed`);
|
|
1640
|
-
} else {
|
|
1641
|
-
await prompts.log.message('Removal cancelled - module will be kept');
|
|
1642
|
-
keptCount++;
|
|
1643
|
-
}
|
|
1644
|
-
} else {
|
|
1645
|
-
await prompts.log.message('Removal cancelled - module will be kept');
|
|
1646
|
-
keptCount++;
|
|
1647
|
-
}
|
|
1648
|
-
|
|
1649
|
-
break;
|
|
1650
|
-
}
|
|
1651
|
-
case 'keep': {
|
|
1652
|
-
keptCount++;
|
|
1653
|
-
keptModulesWithoutSources.push(missing.id);
|
|
1654
|
-
await prompts.log.message('Module will be kept as-is');
|
|
1655
|
-
|
|
1656
|
-
break;
|
|
1657
|
-
}
|
|
1658
|
-
// No default
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
|
|
1662
|
-
// Show summary
|
|
1663
|
-
if (keptCount > 0 || updatedCount > 0 || removedCount > 0) {
|
|
1664
|
-
let summary = 'Summary for custom modules with missing sources:';
|
|
1665
|
-
if (keptCount > 0) summary += `\n • ${keptCount} module(s) kept as-is`;
|
|
1666
|
-
if (updatedCount > 0) summary += `\n • ${updatedCount} module(s) updated with new sources`;
|
|
1667
|
-
if (removedCount > 0) summary += `\n • ${removedCount} module(s) permanently deleted`;
|
|
1668
|
-
await prompts.log.message(summary);
|
|
1669
|
-
}
|
|
1670
|
-
|
|
1671
|
-
return {
|
|
1672
|
-
validCustomModules,
|
|
1673
|
-
keptModulesWithoutSources,
|
|
1674
|
-
};
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
1389
|
/**
|
|
1678
1390
|
* Find the bmad installation directory in a project
|
|
1679
1391
|
* Always uses the standard _bmad folder name
|