bmad-method 6.2.3-next.9 → 6.3.1-next.0
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 -3
- package/README.md +8 -9
- package/README_CN.md +1 -1
- package/README_VN.md +109 -0
- package/package.json +1 -1
- package/removals.txt +17 -0
- 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-edit-prd/data/prd-purpose.md +197 -0
- 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/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 +1 -1
- package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +1 -1
- package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +5 -0
- 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-code-review/steps/step-04-present.md +14 -17
- 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 +2 -0
- package/src/core-skills/bmad-help/SKILL.md +4 -2
- package/src/core-skills/bmad-party-mode/SKILL.md +8 -6
- package/src/core-skills/module-help.csv +1 -0
- package/tools/installer/cli-utils.js +18 -9
- package/tools/installer/commands/install.js +1 -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 +180 -463
- package/tools/installer/core/manifest-generator.js +8 -14
- package/tools/installer/core/manifest.js +94 -102
- package/tools/installer/ide/_config-driven.js +149 -38
- 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 +644 -0
- package/tools/installer/modules/external-manager.js +65 -49
- package/tools/installer/modules/official-modules.js +117 -65
- package/tools/installer/modules/plugin-resolver.js +398 -0
- 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 +549 -666
- package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -61
- 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 -53
- 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 -55
- package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
- 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
|
@@ -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 {
|
|
@@ -548,6 +569,7 @@ class Installer {
|
|
|
548
569
|
*/
|
|
549
570
|
async _installOfficialModules(config, paths, officialModuleIds, addResult, isQuickUpdate, officialModules, ctx) {
|
|
550
571
|
const { message, installedModuleNames } = ctx;
|
|
572
|
+
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
551
573
|
|
|
552
574
|
for (const moduleName of officialModuleIds) {
|
|
553
575
|
if (installedModuleNames.has(moduleName)) continue;
|
|
@@ -556,7 +578,7 @@ class Installer {
|
|
|
556
578
|
message(`${isQuickUpdate ? 'Updating' : 'Installing'} ${moduleName}...`);
|
|
557
579
|
|
|
558
580
|
const moduleConfig = officialModules.moduleConfigs[moduleName] || {};
|
|
559
|
-
await officialModules.install(
|
|
581
|
+
const installResult = await officialModules.install(
|
|
560
582
|
moduleName,
|
|
561
583
|
paths.bmadDir,
|
|
562
584
|
(filePath) => {
|
|
@@ -570,35 +592,16 @@ class Installer {
|
|
|
570
592
|
},
|
|
571
593
|
);
|
|
572
594
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
/**
|
|
578
|
-
* Install custom modules using CustomModules.install().
|
|
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);
|
|
595
|
+
// Get display name from source module.yaml; version from resolution cache or marketplace.json
|
|
596
|
+
const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
|
|
597
|
+
const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
|
|
598
|
+
const displayName = moduleInfo?.name || moduleName;
|
|
588
599
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const
|
|
592
|
-
const
|
|
593
|
-
|
|
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');
|
|
600
|
+
// Prefer version from resolution cache (accurate for custom/local modules),
|
|
601
|
+
// fall back to marketplace.json walk-up for official modules
|
|
602
|
+
const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
|
|
603
|
+
const version = cachedResolution?.version || (sourcePath ? await this._getMarketplaceVersion(sourcePath) : '');
|
|
604
|
+
addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
|
|
602
605
|
}
|
|
603
606
|
}
|
|
604
607
|
|
|
@@ -971,6 +974,14 @@ class Installer {
|
|
|
971
974
|
outputs,
|
|
972
975
|
] = columns;
|
|
973
976
|
|
|
977
|
+
// Pass through _meta rows as-is (module metadata, not a skill)
|
|
978
|
+
if (phase === '_meta') {
|
|
979
|
+
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
|
980
|
+
const metaRow = [finalModule, '_meta', '', '', '', '', '', 'false', '', '', '', '', '', '', outputLocation || '', ''];
|
|
981
|
+
allRows.push(metaRow.map((c) => this.escapeCSVField(c)).join(','));
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
|
|
974
985
|
// If module column is empty, set it to this module's name (except for core which stays empty for universal tools)
|
|
975
986
|
const finalModule = (!module || module.trim() === '') && moduleName !== 'core' ? moduleName : module || '';
|
|
976
987
|
|
|
@@ -1062,23 +1073,10 @@ class Installer {
|
|
|
1062
1073
|
const selectedIdes = new Set((context.ides || []).map((ide) => String(ide).toLowerCase()));
|
|
1063
1074
|
|
|
1064
1075
|
// Build step lines with status indicators
|
|
1076
|
+
const preVersions = context.preInstallVersions || new Map();
|
|
1065
1077
|
const lines = [];
|
|
1066
1078
|
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
|
-
}
|
|
1079
|
+
const stepLabel = r.step;
|
|
1082
1080
|
|
|
1083
1081
|
let icon;
|
|
1084
1082
|
if (r.status === 'ok') {
|
|
@@ -1088,18 +1086,32 @@ class Installer {
|
|
|
1088
1086
|
} else {
|
|
1089
1087
|
icon = color.red('\u2717');
|
|
1090
1088
|
}
|
|
1091
|
-
|
|
1089
|
+
|
|
1090
|
+
// Build version detail for module results
|
|
1091
|
+
let detail = '';
|
|
1092
|
+
if (r.moduleCode && r.newVersion) {
|
|
1093
|
+
const oldVersion = preVersions.get(r.moduleCode);
|
|
1094
|
+
if (oldVersion && oldVersion === r.newVersion) {
|
|
1095
|
+
detail = ` (v${r.newVersion}, no change)`;
|
|
1096
|
+
} else if (oldVersion) {
|
|
1097
|
+
detail = ` (v${oldVersion} → v${r.newVersion})`;
|
|
1098
|
+
} else {
|
|
1099
|
+
detail = ` (v${r.newVersion}, installed)`;
|
|
1100
|
+
}
|
|
1101
|
+
} else if (r.detail) {
|
|
1102
|
+
detail = ` (${r.detail})`;
|
|
1103
|
+
}
|
|
1092
1104
|
lines.push(` ${icon} ${stepLabel}${detail}`);
|
|
1093
1105
|
}
|
|
1094
1106
|
|
|
1095
1107
|
if ((context.ides || []).length === 0) {
|
|
1096
|
-
lines.push(` ${color.green('\u2713')} No IDE selected
|
|
1108
|
+
lines.push(` ${color.green('\u2713')} No IDE selected (installed in _bmad only)`);
|
|
1097
1109
|
}
|
|
1098
1110
|
|
|
1099
1111
|
// Context and warnings
|
|
1100
1112
|
lines.push('');
|
|
1101
1113
|
if (context.bmadDir) {
|
|
1102
|
-
lines.push(` Installed to: ${
|
|
1114
|
+
lines.push(` Installed to: ${context.bmadDir}`);
|
|
1103
1115
|
}
|
|
1104
1116
|
if (context.customFiles && context.customFiles.length > 0) {
|
|
1105
1117
|
lines.push(` ${color.cyan(`Custom files preserved: ${context.customFiles.length}`)}`);
|
|
@@ -1111,17 +1123,18 @@ class Installer {
|
|
|
1111
1123
|
// Next steps
|
|
1112
1124
|
lines.push(
|
|
1113
1125
|
'',
|
|
1114
|
-
'
|
|
1115
|
-
`
|
|
1116
|
-
`
|
|
1117
|
-
|
|
1118
|
-
`
|
|
1126
|
+
' Get started:',
|
|
1127
|
+
` 1. Launch your AI agent from your project folder`,
|
|
1128
|
+
` 2. Not sure what to do? Invoke the ${color.cyan('bmad-help')} skill and ask it what to do!`,
|
|
1129
|
+
'',
|
|
1130
|
+
` Blog, Docs and Guides: ${color.blue('https://bmadcode.com/')}`,
|
|
1131
|
+
` Community: ${color.blue('https://discord.gg/gk8jAdXWmj')}`,
|
|
1119
1132
|
);
|
|
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
1133
|
|
|
1124
|
-
await prompts.
|
|
1134
|
+
await prompts.box(lines.join('\n'), 'BMAD is ready to use!', {
|
|
1135
|
+
rounded: true,
|
|
1136
|
+
formatBorder: color.green,
|
|
1137
|
+
});
|
|
1125
1138
|
}
|
|
1126
1139
|
|
|
1127
1140
|
/**
|
|
@@ -1144,63 +1157,9 @@ class Installer {
|
|
|
1144
1157
|
const configuredIdes = existingInstall.ides;
|
|
1145
1158
|
const projectRoot = path.dirname(bmadDir);
|
|
1146
1159
|
|
|
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
1160
|
// Get available modules (what we have source for)
|
|
1202
1161
|
const availableModulesData = await new OfficialModules().listAvailable();
|
|
1203
|
-
const availableModules = [...availableModulesData.modules
|
|
1162
|
+
const availableModules = [...availableModulesData.modules];
|
|
1204
1163
|
|
|
1205
1164
|
// Add external official modules to available modules
|
|
1206
1165
|
const externalModules = await this.externalModuleManager.listAvailable();
|
|
@@ -1215,52 +1174,44 @@ class Installer {
|
|
|
1215
1174
|
}
|
|
1216
1175
|
}
|
|
1217
1176
|
|
|
1218
|
-
// Add
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1177
|
+
// Add installed community modules to available modules
|
|
1178
|
+
const { CommunityModuleManager } = require('../modules/community-manager');
|
|
1179
|
+
const communityMgr = new CommunityModuleManager();
|
|
1180
|
+
const communityModules = await communityMgr.listAll();
|
|
1181
|
+
for (const communityModule of communityModules) {
|
|
1182
|
+
if (installedModules.includes(communityModule.code) && !availableModules.some((m) => m.id === communityModule.code)) {
|
|
1222
1183
|
availableModules.push({
|
|
1223
|
-
id:
|
|
1224
|
-
name:
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
fromManifest: true,
|
|
1184
|
+
id: communityModule.code,
|
|
1185
|
+
name: communityModule.displayName,
|
|
1186
|
+
isExternal: true,
|
|
1187
|
+
fromCommunity: true,
|
|
1228
1188
|
});
|
|
1229
1189
|
}
|
|
1230
1190
|
}
|
|
1231
1191
|
|
|
1232
|
-
//
|
|
1233
|
-
const
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
}));
|
|
1192
|
+
// Add installed custom modules to available modules
|
|
1193
|
+
const { CustomModuleManager } = require('../modules/custom-module-manager');
|
|
1194
|
+
const customMgr = new CustomModuleManager();
|
|
1195
|
+
for (const moduleId of installedModules) {
|
|
1196
|
+
if (!availableModules.some((m) => m.id === moduleId)) {
|
|
1197
|
+
const customSource = await customMgr.findModuleSourceByCode(moduleId, { bmadDir });
|
|
1198
|
+
if (customSource) {
|
|
1199
|
+
availableModules.push({
|
|
1200
|
+
id: moduleId,
|
|
1201
|
+
name: moduleId,
|
|
1202
|
+
isExternal: true,
|
|
1203
|
+
fromCustom: true,
|
|
1204
|
+
});
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1249
1208
|
|
|
1250
|
-
const
|
|
1251
|
-
const availableModuleIds = new Set(allAvailableModules.map((m) => m.id));
|
|
1209
|
+
const availableModuleIds = new Set(availableModules.map((m) => m.id));
|
|
1252
1210
|
|
|
1253
1211
|
// Only update modules that are BOTH installed AND available (we have source for)
|
|
1254
1212
|
const modulesToUpdate = installedModules.filter((id) => availableModuleIds.has(id));
|
|
1255
1213
|
const skippedModules = installedModules.filter((id) => !availableModuleIds.has(id));
|
|
1256
1214
|
|
|
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
1215
|
if (skippedModules.length > 0) {
|
|
1265
1216
|
await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
|
|
1266
1217
|
}
|
|
@@ -1278,6 +1229,7 @@ class Installer {
|
|
|
1278
1229
|
}
|
|
1279
1230
|
|
|
1280
1231
|
for (const moduleName of modulesToUpdate) {
|
|
1232
|
+
if (moduleName === 'core') continue; // Already collected above
|
|
1281
1233
|
const modulePrompted = await quickModules.collectModuleConfigQuick(moduleName, projectDir, true);
|
|
1282
1234
|
if (modulePrompted) {
|
|
1283
1235
|
promptedForNewFields = true;
|
|
@@ -1304,9 +1256,7 @@ class Installer {
|
|
|
1304
1256
|
actionType: 'install',
|
|
1305
1257
|
_quickUpdate: true,
|
|
1306
1258
|
_preserveModules: skippedModules,
|
|
1307
|
-
_customModuleSources: customModuleSources,
|
|
1308
1259
|
_existingModules: installedModules,
|
|
1309
|
-
customContent: config.customContent,
|
|
1310
1260
|
};
|
|
1311
1261
|
|
|
1312
1262
|
await this.install(installConfig);
|
|
@@ -1441,239 +1391,6 @@ class Installer {
|
|
|
1441
1391
|
return this._readOutputFolder(bmadDir);
|
|
1442
1392
|
}
|
|
1443
1393
|
|
|
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
1394
|
/**
|
|
1678
1395
|
* Find the bmad installation directory in a project
|
|
1679
1396
|
* Always uses the standard _bmad folder name
|