bmad-method 6.3.1-next.9 → 6.4.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.
Files changed (140) hide show
  1. package/package.json +3 -2
  2. package/src/bmm-skills/1-analysis/bmad-agent-analyst/SKILL.md +51 -36
  3. package/src/bmm-skills/1-analysis/bmad-agent-analyst/customize.toml +90 -0
  4. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/SKILL.md +50 -33
  5. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/customize.toml +81 -0
  6. package/src/bmm-skills/1-analysis/bmad-document-project/SKILL.md +57 -1
  7. package/src/bmm-skills/1-analysis/bmad-document-project/customize.toml +41 -0
  8. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/deep-dive-instructions.md +1 -0
  9. package/src/bmm-skills/1-analysis/bmad-document-project/workflows/full-scan-instructions.md +1 -0
  10. package/src/bmm-skills/1-analysis/bmad-prfaq/SKILL.md +48 -9
  11. package/src/bmm-skills/1-analysis/bmad-prfaq/customize.toml +41 -0
  12. package/src/bmm-skills/1-analysis/bmad-prfaq/references/verdict.md +4 -0
  13. package/src/bmm-skills/1-analysis/bmad-product-brief/SKILL.md +44 -9
  14. package/src/bmm-skills/1-analysis/bmad-product-brief/customize.toml +47 -0
  15. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/contextual-discovery.md +8 -7
  16. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/draft-and-review.md +6 -5
  17. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/finalize.md +4 -1
  18. package/src/bmm-skills/1-analysis/bmad-product-brief/prompts/guided-elicitation.md +3 -2
  19. package/src/bmm-skills/1-analysis/research/bmad-domain-research/SKILL.md +91 -1
  20. package/src/bmm-skills/1-analysis/research/bmad-domain-research/customize.toml +41 -0
  21. package/src/bmm-skills/1-analysis/research/bmad-domain-research/domain-steps/step-06-research-synthesis.md +6 -0
  22. package/src/bmm-skills/1-analysis/research/bmad-market-research/SKILL.md +91 -1
  23. package/src/bmm-skills/1-analysis/research/bmad-market-research/customize.toml +41 -0
  24. package/src/bmm-skills/1-analysis/research/bmad-market-research/steps/step-06-research-completion.md +6 -0
  25. package/src/bmm-skills/1-analysis/research/bmad-technical-research/SKILL.md +91 -1
  26. package/src/bmm-skills/1-analysis/research/bmad-technical-research/customize.toml +41 -0
  27. package/src/bmm-skills/1-analysis/research/bmad-technical-research/technical-steps/step-06-research-synthesis.md +6 -0
  28. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/SKILL.md +50 -35
  29. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/customize.toml +85 -0
  30. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/SKILL.md +50 -31
  31. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/customize.toml +60 -0
  32. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/SKILL.md +99 -1
  33. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/customize.toml +41 -0
  34. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
  35. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
  36. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
  37. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
  38. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
  39. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  40. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
  41. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
  42. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  43. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
  44. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
  45. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  46. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
  47. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  48. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
  49. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
  50. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  51. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
  52. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
  53. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  54. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
  55. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
  56. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  57. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
  58. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
  59. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
  60. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/SKILL.md +46 -7
  61. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/customize.toml +41 -0
  62. package/src/bmm-skills/4-implementation/bmad-checkpoint-preview/step-05-wrapup.md +6 -0
  63. package/src/bmm-skills/4-implementation/bmad-code-review/SKILL.md +85 -1
  64. package/src/bmm-skills/4-implementation/bmad-code-review/customize.toml +41 -0
  65. package/src/bmm-skills/4-implementation/bmad-code-review/steps/step-04-present.md +6 -0
  66. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
  67. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  68. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +424 -1
  69. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  70. package/src/bmm-skills/4-implementation/bmad-dev-story/SKILL.md +480 -1
  71. package/src/bmm-skills/4-implementation/bmad-dev-story/customize.toml +41 -0
  72. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
  73. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  74. package/src/bmm-skills/4-implementation/bmad-quick-dev/SKILL.md +106 -1
  75. package/src/bmm-skills/4-implementation/bmad-quick-dev/customize.toml +41 -0
  76. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-05-present.md +6 -0
  77. package/src/bmm-skills/4-implementation/bmad-quick-dev/step-oneshot.md +6 -0
  78. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
  79. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  80. package/src/bmm-skills/4-implementation/bmad-sprint-planning/SKILL.md +294 -1
  81. package/src/bmm-skills/4-implementation/bmad-sprint-planning/customize.toml +41 -0
  82. package/src/bmm-skills/4-implementation/bmad-sprint-status/SKILL.md +292 -1
  83. package/src/bmm-skills/4-implementation/bmad-sprint-status/customize.toml +41 -0
  84. package/src/bmm-skills/module.yaml +49 -0
  85. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
  86. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  87. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  88. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  89. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
  90. package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
  91. package/src/core-skills/module-help.csv +1 -0
  92. package/src/core-skills/module.yaml +2 -0
  93. package/src/scripts/resolve_config.py +176 -0
  94. package/src/scripts/resolve_customization.py +230 -0
  95. package/tools/installer/commands/install.js +13 -0
  96. package/tools/installer/core/config.js +4 -1
  97. package/tools/installer/core/install-paths.js +11 -5
  98. package/tools/installer/core/installer.js +181 -94
  99. package/tools/installer/core/manifest-generator.js +339 -184
  100. package/tools/installer/core/manifest.js +86 -86
  101. package/tools/installer/ide/platform-codes.yaml +6 -0
  102. package/tools/installer/modules/channel-plan.js +203 -0
  103. package/tools/installer/modules/channel-resolver.js +241 -0
  104. package/tools/installer/modules/community-manager.js +130 -23
  105. package/tools/installer/modules/custom-module-manager.js +160 -19
  106. package/tools/installer/modules/external-manager.js +235 -32
  107. package/tools/installer/modules/official-modules.js +58 -12
  108. package/tools/installer/modules/registry-client.js +139 -7
  109. package/tools/installer/modules/registry-fallback.yaml +8 -0
  110. package/tools/installer/modules/version-resolver.js +336 -0
  111. package/tools/installer/project-root.js +54 -0
  112. package/tools/installer/ui.js +561 -50
  113. package/tools/platform-codes.yaml +6 -0
  114. package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
  115. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
  116. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
  117. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
  118. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
  119. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
  120. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
  121. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
  122. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
  123. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
  124. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
  125. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
  126. package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
  127. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
  128. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
  129. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
  130. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
  131. package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
  132. package/src/bmm-skills/4-implementation/bmad-code-review/workflow.md +0 -55
  133. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
  134. package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
  135. package/src/bmm-skills/4-implementation/bmad-dev-story/workflow.md +0 -450
  136. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
  137. package/src/bmm-skills/4-implementation/bmad-quick-dev/workflow.md +0 -76
  138. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
  139. package/src/bmm-skills/4-implementation/bmad-sprint-planning/workflow.md +0 -263
  140. package/src/bmm-skills/4-implementation/bmad-sprint-status/workflow.md +0 -261
@@ -11,6 +11,7 @@ const prompts = require('../prompts');
11
11
  const { BMAD_FOLDER_NAME } = require('../ide/shared/path-utils');
12
12
  const { InstallPaths } = require('./install-paths');
13
13
  const { ExternalModuleManager } = require('../modules/external-manager');
14
+ const { resolveModuleVersion } = require('../modules/version-resolver');
14
15
 
15
16
  const { ExistingInstall } = require('./existing-install');
16
17
 
@@ -24,44 +25,6 @@ class Installer {
24
25
  this.bmadFolderName = BMAD_FOLDER_NAME;
25
26
  }
26
27
 
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
-
65
28
  /**
66
29
  * Main installation method
67
30
  * @param {Object} config - Installation configuration
@@ -244,6 +207,15 @@ class Installer {
244
207
 
245
208
  const installTasks = [];
246
209
 
210
+ installTasks.push({
211
+ title: 'Installing shared scripts',
212
+ task: async () => {
213
+ await this._installSharedScripts(paths);
214
+ addResult('Shared scripts', 'ok');
215
+ return 'Shared scripts installed';
216
+ },
217
+ });
218
+
247
219
  if (allModules.length > 0) {
248
220
  installTasks.push({
249
221
  title: isQuickUpdate ? `Updating ${allModules.length} module(s)` : `Installing ${allModules.length} module(s)`,
@@ -301,7 +273,8 @@ class Installer {
301
273
  addResult('Configurations', 'ok', 'generated');
302
274
 
303
275
  this.installedFiles.add(paths.manifestFile());
304
- this.installedFiles.add(paths.agentManifest());
276
+ this.installedFiles.add(paths.centralConfig());
277
+ this.installedFiles.add(paths.centralUserConfig());
305
278
 
306
279
  message('Generating manifests...');
307
280
  const manifestGen = new ManifestGenerator();
@@ -322,10 +295,11 @@ class Installer {
322
295
  await manifestGen.generateManifests(paths.bmadDir, allModulesForManifest, [...this.installedFiles], {
323
296
  ides: config.ides || [],
324
297
  preservedModules: modulesForCsvPreserve,
298
+ moduleConfigs,
325
299
  });
326
300
 
327
301
  message('Generating help catalog...');
328
- await this.mergeModuleHelpCatalogs(paths.bmadDir);
302
+ await this.mergeModuleHelpCatalogs(paths.bmadDir, manifestGen.agents);
329
303
  addResult('Help catalog', 'ok');
330
304
 
331
305
  return 'Configurations generated';
@@ -558,6 +532,44 @@ class Installer {
558
532
  return { tempBackupDir, tempModifiedBackupDir };
559
533
  }
560
534
 
535
+ /**
536
+ * Sync src/scripts/* → _bmad/scripts/ so shared Python scripts
537
+ * (e.g. resolve_customization.py) are available at install time.
538
+ * Wipes the destination first so files removed or renamed in source
539
+ * don't linger and get recorded as installed. Also seeds
540
+ * _bmad/custom/.gitignore on fresh installs so *.user.toml overrides
541
+ * stay out of version control.
542
+ */
543
+ async _installSharedScripts(paths) {
544
+ const srcScriptsDir = path.join(paths.srcDir, 'src', 'scripts');
545
+ if (!(await fs.pathExists(srcScriptsDir))) {
546
+ throw new Error(`Shared scripts source directory not found: ${srcScriptsDir}`);
547
+ }
548
+
549
+ await fs.remove(paths.scriptsDir);
550
+ await fs.ensureDir(paths.scriptsDir);
551
+ await fs.copy(srcScriptsDir, paths.scriptsDir, { overwrite: true });
552
+ await this._trackFilesRecursive(paths.scriptsDir);
553
+
554
+ const customGitignore = path.join(paths.customDir, '.gitignore');
555
+ if (!(await fs.pathExists(customGitignore))) {
556
+ await fs.writeFile(customGitignore, '*.user.toml\n', 'utf8');
557
+ this.installedFiles.add(customGitignore);
558
+ }
559
+ }
560
+
561
+ async _trackFilesRecursive(dir) {
562
+ const entries = await fs.readdir(dir, { withFileTypes: true });
563
+ for (const entry of entries) {
564
+ const full = path.join(dir, entry.name);
565
+ if (entry.isDirectory()) {
566
+ await this._trackFilesRecursive(full);
567
+ } else if (entry.isFile()) {
568
+ this.installedFiles.add(full);
569
+ }
570
+ }
571
+ }
572
+
561
573
  /**
562
574
  * Install official (non-custom) modules.
563
575
  * @param {Object} config - Installation configuration
@@ -589,19 +601,40 @@ class Installer {
589
601
  moduleConfig: moduleConfig,
590
602
  installer: this,
591
603
  silent: true,
604
+ channelOptions: config.channelOptions,
592
605
  },
593
606
  );
594
607
 
595
- // Get display name from source module.yaml; version from resolution cache or marketplace.json
596
- const sourcePath = await officialModules.findModuleSource(moduleName, { silent: true });
608
+ // Get display name from source module.yaml and resolve the freshest version metadata we can find locally.
609
+ const sourcePath = await officialModules.findModuleSource(moduleName, {
610
+ silent: true,
611
+ channelOptions: config.channelOptions,
612
+ });
597
613
  const moduleInfo = sourcePath ? await officialModules.getModuleInfo(sourcePath, moduleName, '') : null;
598
614
  const displayName = moduleInfo?.name || moduleName;
599
615
 
600
- // Prefer version from resolution cache (accurate for custom/local modules),
601
- // fall back to marketplace.json walk-up for official modules
616
+ const externalResolution = officialModules.externalModuleManager.getResolution(moduleName);
617
+ let communityResolution = null;
618
+ if (!externalResolution) {
619
+ const { CommunityModuleManager } = require('../modules/community-manager');
620
+ communityResolution = new CommunityModuleManager().getResolution(moduleName);
621
+ }
622
+ const resolution = externalResolution || communityResolution;
602
623
  const cachedResolution = CustomModuleManager._resolutionCache.get(moduleName);
603
- const version = cachedResolution?.version || (sourcePath ? await this._getMarketplaceVersion(sourcePath) : '');
604
- addResult(displayName, 'ok', '', { moduleCode: moduleName, newVersion: version });
624
+ const versionInfo = await resolveModuleVersion(moduleName, {
625
+ moduleSourcePath: sourcePath,
626
+ fallbackVersion: resolution?.version || cachedResolution?.version,
627
+ marketplacePluginNames: cachedResolution?.pluginName ? [cachedResolution.pluginName] : [],
628
+ });
629
+ // Prefer the git tag recorded by the resolution (e.g. "v1.7.0") over
630
+ // the on-disk package.json (which may be ahead of the released tag).
631
+ const version = resolution?.version || versionInfo.version || '';
632
+ addResult(displayName, 'ok', '', {
633
+ moduleCode: moduleName,
634
+ newVersion: version,
635
+ newChannel: resolution?.channel || null,
636
+ newSha: resolution?.sha || null,
637
+ });
605
638
  }
606
639
  }
607
640
 
@@ -671,8 +704,11 @@ class Installer {
671
704
  const customFiles = [];
672
705
  const modifiedFiles = [];
673
706
 
674
- // Memory is always in _bmad/_memory
675
- const bmadMemoryPath = '_memory';
707
+ // Memory subtrees (v6.1: _bmad/_memory, current: _bmad/memory) hold
708
+ // per-user runtime data generated by agents with sidecars. These files
709
+ // aren't installer-managed and must never be reported as "custom" or
710
+ // "modified" — they're user state, not user overrides.
711
+ const bmadMemoryPaths = ['_memory', 'memory'];
676
712
 
677
713
  // Check if the manifest has hashes - if not, we can't detect modifications
678
714
  let manifestHasHashes = false;
@@ -738,7 +774,7 @@ class Installer {
738
774
  continue;
739
775
  }
740
776
 
741
- if (relativePath.startsWith(bmadMemoryPath + '/') && path.dirname(relativePath).includes('-sidecar')) {
777
+ if (bmadMemoryPaths.some((mp) => relativePath === mp || relativePath.startsWith(mp + '/'))) {
742
778
  continue;
743
779
  }
744
780
 
@@ -789,9 +825,8 @@ class Installer {
789
825
 
790
826
  // Get all installed module directories
791
827
  const entries = await fs.readdir(bmadDir, { withFileTypes: true });
792
- const installedModules = entries
793
- .filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs')
794
- .map((entry) => entry.name);
828
+ const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
829
+ const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name);
795
830
 
796
831
  // Generate config.yaml for each installed module
797
832
  for (const moduleName of installedModules) {
@@ -873,53 +908,36 @@ class Installer {
873
908
  }
874
909
 
875
910
  /**
876
- * Merge all module-help.csv files into a single bmad-help.csv
877
- * Scans all installed modules for module-help.csv and merges them
878
- * Enriches agent info from agent-manifest.csv
879
- * Output is written to _bmad/_config/bmad-help.csv
911
+ * Merge all module-help.csv files into a single bmad-help.csv.
912
+ * Scans all installed modules for module-help.csv and merges them.
913
+ * Enriches agent info from the in-memory agent list produced by ManifestGenerator.
914
+ * Output is written to _bmad/_config/bmad-help.csv.
880
915
  * @param {string} bmadDir - BMAD installation directory
916
+ * @param {Array<Object>} agentEntries - Agents collected from module.yaml (code, name, title, icon, module, ...)
881
917
  */
882
- async mergeModuleHelpCatalogs(bmadDir) {
918
+ async mergeModuleHelpCatalogs(bmadDir, agentEntries = []) {
883
919
  const allRows = [];
884
920
  const headerRow =
885
921
  'module,phase,name,code,sequence,workflow-file,command,required,agent-name,agent-command,agent-display-name,agent-title,options,description,output-location,outputs';
886
922
 
887
- // Load agent manifest for agent info lookup
888
- const agentManifestPath = path.join(bmadDir, '_config', 'agent-manifest.csv');
889
- const agentInfo = new Map(); // agent-name -> {command, displayName, title+icon}
890
-
891
- if (await fs.pathExists(agentManifestPath)) {
892
- const manifestContent = await fs.readFile(agentManifestPath, 'utf8');
893
- const lines = manifestContent.split('\n').filter((line) => line.trim());
894
-
895
- for (const line of lines) {
896
- if (line.startsWith('name,')) continue; // Skip header
897
-
898
- const cols = line.split(',');
899
- if (cols.length >= 4) {
900
- const agentName = cols[0].replaceAll('"', '').trim();
901
- const displayName = cols[1].replaceAll('"', '').trim();
902
- const title = cols[2].replaceAll('"', '').trim();
903
- const icon = cols[3].replaceAll('"', '').trim();
904
- const module = cols[10] ? cols[10].replaceAll('"', '').trim() : '';
905
-
906
- // Build agent command: bmad:module:agent:name
907
- const agentCommand = module ? `bmad:${module}:agent:${agentName}` : `bmad:agent:${agentName}`;
908
-
909
- agentInfo.set(agentName, {
910
- command: agentCommand,
911
- displayName: displayName || agentName,
912
- title: icon && title ? `${icon} ${title}` : title || agentName,
913
- });
914
- }
915
- }
923
+ // Build agent lookup from the in-memory list (agent code → command + display fields).
924
+ const agentInfo = new Map();
925
+ for (const agent of agentEntries) {
926
+ if (!agent || !agent.code) continue;
927
+ const agentCommand = agent.module ? `bmad:${agent.module}:agent:${agent.code}` : `bmad:agent:${agent.code}`;
928
+ const displayName = agent.name || agent.code;
929
+ const titleCombined = agent.icon && agent.title ? `${agent.icon} ${agent.title}` : agent.title || agent.code;
930
+ agentInfo.set(agent.code, {
931
+ command: agentCommand,
932
+ displayName,
933
+ title: titleCombined,
934
+ });
916
935
  }
917
936
 
918
937
  // Get all installed module directories
919
938
  const entries = await fs.readdir(bmadDir, { withFileTypes: true });
920
- const installedModules = entries
921
- .filter((entry) => entry.isDirectory() && entry.name !== '_config' && entry.name !== 'docs' && entry.name !== '_memory')
922
- .map((entry) => entry.name);
939
+ const nonModuleDirs = new Set(['_config', '_memory', 'memory', 'docs', 'scripts', 'custom']);
940
+ const installedModules = entries.filter((entry) => entry.isDirectory() && !nonModuleDirs.has(entry.name)).map((entry) => entry.name);
923
941
 
924
942
  // Add core module to scan (it's installed at root level as _config, but we check src/core-skills)
925
943
  const coreModulePath = getSourcePath('core-skills');
@@ -1091,12 +1109,30 @@ class Installer {
1091
1109
  let detail = '';
1092
1110
  if (r.moduleCode && r.newVersion) {
1093
1111
  const oldVersion = preVersions.get(r.moduleCode);
1094
- if (oldVersion && oldVersion === r.newVersion) {
1095
- detail = ` (v${r.newVersion}, no change)`;
1112
+ // Format a version label for display:
1113
+ // "main" "main @ <short-sha>" (next channel shows what SHA landed)
1114
+ // "v1.7.0" or "1.7.0" → "v1.7.0" (prefix 'v' when missing)
1115
+ // anything else (legacy strings) → as-is
1116
+ const fmt = (v, sha) => {
1117
+ if (typeof v !== 'string' || !v) return '';
1118
+ if (v === 'main' || v === 'HEAD') return sha ? `main @ ${sha.slice(0, 7)}` : 'main';
1119
+ if (/^v?\d+\.\d+\.\d+/.test(v)) return v.startsWith('v') ? v : `v${v}`;
1120
+ return v;
1121
+ };
1122
+ const newV = fmt(r.newVersion, r.newSha);
1123
+ // 'main'/'HEAD' strings only identify the channel, not the commit, so
1124
+ // we can't assert "no change" without comparing SHAs — and preVersions
1125
+ // doesn't carry the old SHA. Render these as a refresh instead of a
1126
+ // false-negative "no change".
1127
+ const isMainLike = oldVersion === 'main' || oldVersion === 'HEAD';
1128
+ if (oldVersion && oldVersion === r.newVersion && !isMainLike) {
1129
+ detail = ` (${newV}, no change)`;
1130
+ } else if (oldVersion && isMainLike) {
1131
+ detail = ` (${newV}, refreshed)`;
1096
1132
  } else if (oldVersion) {
1097
- detail = ` (v${oldVersion} → v${r.newVersion})`;
1133
+ detail = ` (${fmt(oldVersion, r.newSha)} → ${newV})`;
1098
1134
  } else {
1099
- detail = ` (v${r.newVersion}, installed)`;
1135
+ detail = ` (${newV}, installed)`;
1100
1136
  }
1101
1137
  } else if (r.detail) {
1102
1138
  detail = ` (${r.detail})`;
@@ -1216,9 +1252,59 @@ class Installer {
1216
1252
  await prompts.log.warn(`Skipping ${skippedModules.length} module(s) - no source available: ${skippedModules.join(', ')}`);
1217
1253
  }
1218
1254
 
1255
+ // Build channel options from the existing manifest FIRST so the config
1256
+ // collector below (which triggers external-module clones via
1257
+ // findModuleSource) knows each module's recorded channel and doesn't
1258
+ // silently redecide it. Without this, modules previously on 'next' or
1259
+ // 'pinned' would trigger a stable-channel tag lookup at config-collection
1260
+ // time, burning GitHub API quota and potentially failing.
1261
+ const manifestData = await this.manifest.read(bmadDir);
1262
+ const channelOptions = { global: null, nextSet: new Set(), pins: new Map(), warnings: [] };
1263
+ if (manifestData?.modulesDetailed) {
1264
+ const { fetchStableTags, classifyUpgrade, parseGitHubRepo } = require('../modules/channel-resolver');
1265
+ for (const entry of manifestData.modulesDetailed) {
1266
+ if (!entry?.name || !entry?.channel) continue;
1267
+ if (entry.channel === 'pinned' && entry.version) {
1268
+ channelOptions.pins.set(entry.name, entry.version);
1269
+ continue;
1270
+ }
1271
+ if (entry.channel === 'next') {
1272
+ channelOptions.nextSet.add(entry.name);
1273
+ continue;
1274
+ }
1275
+ // Stable: classify the available upgrade. Patches and minors fall
1276
+ // through (stable default picks up the top tag). A major upgrade
1277
+ // requires opt-in, so under quick-update's non-interactive semantics
1278
+ // we pin to the current version to prevent a silent breaking jump.
1279
+ if (entry.channel === 'stable' && entry.version && entry.repoUrl) {
1280
+ const parsed = parseGitHubRepo(entry.repoUrl);
1281
+ if (!parsed) continue;
1282
+ try {
1283
+ const tags = await fetchStableTags(parsed.owner, parsed.repo);
1284
+ if (tags.length === 0) continue;
1285
+ const topTag = tags[0].tag;
1286
+ const cls = classifyUpgrade(entry.version, topTag);
1287
+ if (cls === 'major') {
1288
+ channelOptions.pins.set(entry.name, entry.version);
1289
+ await prompts.log.warn(
1290
+ `${entry.name} ${entry.version} → ${topTag} is a new major release; staying on ${entry.version}. ` +
1291
+ `Run \`bmad install\` (Modify) with \`--pin ${entry.name}=${topTag}\` to accept.`,
1292
+ );
1293
+ }
1294
+ } catch (error) {
1295
+ // Tag lookup failed (offline, rate-limited). Stay on the current
1296
+ // version rather than guessing — the existing cache is already
1297
+ // at that ref, so re-using it keeps the install stable.
1298
+ channelOptions.pins.set(entry.name, entry.version);
1299
+ await prompts.log.warn(`Could not check ${entry.name} for updates (${error.message}); staying on ${entry.version}.`);
1300
+ }
1301
+ }
1302
+ }
1303
+ }
1304
+
1219
1305
  // Load existing configs and collect new fields (if any)
1220
1306
  await prompts.log.info('Checking for new configuration options...');
1221
- const quickModules = new OfficialModules();
1307
+ const quickModules = new OfficialModules({ channelOptions });
1222
1308
  await quickModules.loadExistingConfig(projectDir);
1223
1309
 
1224
1310
  let promptedForNewFields = false;
@@ -1257,6 +1343,7 @@ class Installer {
1257
1343
  _quickUpdate: true,
1258
1344
  _preserveModules: skippedModules,
1259
1345
  _existingModules: installedModules,
1346
+ channelOptions,
1260
1347
  };
1261
1348
 
1262
1349
  await this.install(installConfig);