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.
Files changed (127) hide show
  1. package/.claude-plugin/marketplace.json +0 -4
  2. package/README.md +8 -9
  3. package/README_CN.md +1 -1
  4. package/README_VN.md +110 -0
  5. package/package.json +2 -1
  6. package/removals.txt +17 -0
  7. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +7 -4
  8. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +6 -4
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +8 -10
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +96 -0
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/artifact-analyzer.md +60 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/agents/web-researcher.md +49 -0
  13. package/src/bmm-skills/1-analysis/bmad-prfaq/assets/prfaq-template.md +62 -0
  14. package/src/bmm-skills/1-analysis/bmad-prfaq/bmad-manifest.json +16 -0
  15. package/src/bmm-skills/1-analysis/bmad-prfaq/references/customer-faq.md +55 -0
  16. package/src/bmm-skills/1-analysis/bmad-prfaq/references/internal-faq.md +51 -0
  17. package/src/bmm-skills/1-analysis/bmad-prfaq/references/press-release.md +60 -0
  18. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +79 -0
  19. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +1 -6
  20. package/src/bmm-skills/1-analysis/bmad-product-brief/bmad-manifest.json +1 -1
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +8 -6
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +8 -6
  23. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +8 -6
  24. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +6 -4
  25. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +6 -4
  26. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +8 -9
  27. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-13-responsive-accessibility.md +1 -1
  28. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +8 -9
  29. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01-discovery.md +1 -1
  30. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-01b-legacy-conversion.md +1 -1
  31. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-02-review.md +1 -1
  32. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-03-edit.md +1 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +1 -3
  34. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +8 -9
  35. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +8 -9
  36. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +6 -4
  37. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-01-document-discovery.md +1 -1
  38. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-02-prd-analysis.md +1 -1
  39. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-03-epic-coverage-validation.md +1 -1
  40. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +9 -11
  41. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +8 -14
  42. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +10 -12
  43. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +8 -12
  44. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +11 -4
  45. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +29 -0
  46. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/generate-trail.md +38 -0
  47. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-01-orientation.md +105 -0
  48. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-02-walkthrough.md +89 -0
  49. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-03-detail-pass.md +106 -0
  50. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-04-testing.md +74 -0
  51. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +24 -0
  52. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-01-gather-context.md +38 -15
  53. package/src/bmm-skills/4-implementation/bmad-correct-course/checklist.md +2 -2
  54. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +8 -8
  55. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/checklist.md +1 -1
  56. package/src/bmm-skills/4-implementation/bmad-quick-dev/compile-epic-context.md +62 -0
  57. package/src/bmm-skills/4-implementation/bmad-quick-dev/spec-template.md +1 -1
  58. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-01-clarify-and-route.md +33 -6
  59. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-02-plan.md +20 -8
  60. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-03-implement.md +2 -0
  61. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +16 -4
  62. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +1 -5
  63. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +134 -134
  64. package/src/bmm-skills/4-implementation/bmad-sprint-planning/sprint-status-template.yaml +1 -1
  65. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +3 -3
  66. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +2 -2
  67. package/src/bmm-skills/module-help.csv +4 -1
  68. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +1 -2
  69. package/src/core-skills/bmad-distillator/SKILL.md +0 -1
  70. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +9 -9
  71. package/src/core-skills/bmad-help/SKILL.md +4 -2
  72. package/src/core-skills/bmad-party-mode/SKILL.md +121 -2
  73. package/src/core-skills/module-help.csv +1 -0
  74. package/tools/installer/cli-utils.js +18 -9
  75. package/tools/installer/commands/install.js +0 -1
  76. package/tools/installer/core/existing-install.js +2 -8
  77. package/tools/installer/core/install-paths.js +0 -3
  78. package/tools/installer/core/installer.js +176 -464
  79. package/tools/installer/core/manifest-generator.js +4 -12
  80. package/tools/installer/core/manifest.js +82 -97
  81. package/tools/installer/ide/_config-driven.js +149 -38
  82. package/tools/installer/ide/platform-codes.yaml +6 -4
  83. package/tools/installer/ide/shared/skill-manifest.js +1 -16
  84. package/tools/installer/install-messages.yaml +19 -26
  85. package/tools/installer/modules/community-manager.js +377 -0
  86. package/tools/installer/modules/custom-module-manager.js +308 -0
  87. package/tools/installer/modules/external-manager.js +65 -49
  88. package/tools/installer/modules/official-modules.js +37 -65
  89. package/tools/installer/modules/registry-client.js +66 -0
  90. package/tools/installer/{external-official-modules.yaml → modules/registry-fallback.yaml} +3 -12
  91. package/tools/installer/ui.js +340 -672
  92. package/tools/platform-codes.yaml +6 -0
  93. package/src/bmm-skills/2-plan-workflows/create-prd/data/domain-complexity.csv +0 -15
  94. package/src/bmm-skills/2-plan-workflows/create-prd/data/project-types.csv +0 -11
  95. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-01-discovery.md +0 -224
  96. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02-format-detection.md +0 -191
  97. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-02b-parity-check.md +0 -209
  98. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-03-density-validation.md +0 -174
  99. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-04-brief-coverage-validation.md +0 -214
  100. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-05-measurability-validation.md +0 -228
  101. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-06-traceability-validation.md +0 -217
  102. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-07-implementation-leakage-validation.md +0 -205
  103. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-08-domain-compliance-validation.md +0 -243
  104. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-09-project-type-validation.md +0 -263
  105. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-10-smart-validation.md +0 -209
  106. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-11-holistic-quality-validation.md +0 -264
  107. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-12-completeness-validation.md +0 -242
  108. package/src/bmm-skills/2-plan-workflows/create-prd/steps-v/step-v-13-report-complete.md +0 -232
  109. package/src/bmm-skills/2-plan-workflows/create-prd/workflow-validate-prd.md +0 -65
  110. package/src/bmm-skills/4-implementation/bmad-agent-qa/SKILL.md +0 -59
  111. package/src/bmm-skills/4-implementation/bmad-agent-qa/bmad-skill-manifest.yaml +0 -11
  112. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/SKILL.md +0 -51
  113. package/src/bmm-skills/4-implementation/bmad-agent-quick-flow-solo-dev/bmad-skill-manifest.yaml +0 -11
  114. package/src/bmm-skills/4-implementation/bmad-agent-sm/SKILL.md +0 -53
  115. package/src/bmm-skills/4-implementation/bmad-agent-sm/bmad-skill-manifest.yaml +0 -11
  116. package/src/core-skills/bmad-init/SKILL.md +0 -100
  117. package/src/core-skills/bmad-init/resources/core-module.yaml +0 -25
  118. package/src/core-skills/bmad-init/scripts/bmad_init.py +0 -624
  119. package/src/core-skills/bmad-init/scripts/tests/test_bmad_init.py +0 -393
  120. package/src/core-skills/bmad-party-mode/steps/step-01-agent-loading.md +0 -138
  121. package/src/core-skills/bmad-party-mode/steps/step-02-discussion-orchestration.md +0 -187
  122. package/src/core-skills/bmad-party-mode/steps/step-03-graceful-exit.md +0 -167
  123. package/src/core-skills/bmad-party-mode/workflow.md +0 -190
  124. package/tools/installer/core/custom-module-cache.js +0 -260
  125. package/tools/installer/custom-handler.js +0 -112
  126. package/tools/installer/modules/custom-modules.js +0 -197
  127. /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
- await this._cacheCustomModules(paths, addResult);
120
+ const allModules = config.modules || [];
60
121
 
61
- // Compute module lists: official = selected minus custom, all = both
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._installAndConfigure(config, originalConfig, paths, officialModuleIds, allModules, addResult, officialModules);
124
+ await this._setupIdes(config, allModules, paths, addResult, previousSkillIds);
67
125
 
68
- await this._setupIdes(config, allModules, paths, addResult);
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
- * Scan the custom module cache directory and register any cached custom modules
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
- addResult(`Module: ${moduleName}`, 'ok', isQuickUpdate ? 'updated' : 'installed');
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);
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
- let stepLabel = null;
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
- const detail = r.detail ? color.dim(` (${r.detail})`) : '';
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 ${color.dim('(installed in _bmad only)')}`);
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: ${color.dim(context.bmadDir)}`);
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
- ' Next steps:',
1115
- ` Read our new Docs Site: ${color.dim('https://docs.bmad-method.org/')}`,
1116
- ` Join our Discord: ${color.dim('https://discord.gg/gk8jAdXWmj')}`,
1117
- ` Star us on GitHub: ${color.dim('https://github.com/bmad-code-org/BMAD-METHOD/')}`,
1118
- ` Subscribe on YouTube: ${color.dim('https://www.youtube.com/@BMadCode')}`,
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.note(lines.join('\n'), 'BMAD is ready to use!');
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, ...availableModulesData.customModules];
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 custom modules from manifest if their sources exist
1219
- for (const [moduleId, customModule] of customModuleSources) {
1220
- const sourcePath = customModule.sourcePath;
1221
- if (sourcePath && (await fs.pathExists(sourcePath)) && !availableModules.some((m) => m.id === moduleId)) {
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: moduleId,
1224
- name: customModule.name || moduleId,
1225
- path: sourcePath,
1226
- isCustom: true,
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
- // Handle missing custom module sources
1233
- const customModuleResult = await this.handleMissingCustomSources(
1234
- customModuleSources,
1235
- bmadDir,
1236
- projectRoot,
1237
- 'update',
1238
- installedModules,
1239
- config.skipPrompts || false,
1240
- );
1241
-
1242
- const { validCustomModules, keptModulesWithoutSources } = customModuleResult;
1243
-
1244
- const customModulesFromManifest = validCustomModules.map((m) => ({
1245
- ...m,
1246
- isCustom: true,
1247
- hasUpdate: true,
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 allAvailableModules = [...availableModules, ...customModulesFromManifest];
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