bmad-method 6.3.1-next.2 → 6.3.1-next.21

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 (160) hide show
  1. package/package.json +3 -3
  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-08-scoping.md +70 -23
  35. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-11-polish.md +1 -1
  36. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/steps-c/step-12-complete.md +6 -0
  37. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/SKILL.md +70 -1
  38. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/customize.toml +41 -0
  39. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/steps/step-14-complete.md +6 -0
  40. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/SKILL.md +97 -1
  41. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/customize.toml +42 -0
  42. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/steps-e/step-e-04-complete.md +2 -0
  43. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/SKILL.md +99 -1
  44. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/customize.toml +42 -0
  45. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/steps-v/step-v-13-report-complete.md +1 -0
  46. package/src/bmm-skills/3-solutioning/bmad-agent-architect/SKILL.md +50 -30
  47. package/src/bmm-skills/3-solutioning/bmad-agent-architect/customize.toml +65 -0
  48. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/SKILL.md +86 -1
  49. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/customize.toml +41 -0
  50. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/steps/step-06-final-assessment.md +6 -0
  51. package/src/bmm-skills/3-solutioning/bmad-create-architecture/SKILL.md +69 -1
  52. package/src/bmm-skills/3-solutioning/bmad-create-architecture/customize.toml +41 -0
  53. package/src/bmm-skills/3-solutioning/bmad-create-architecture/steps/step-08-complete.md +6 -0
  54. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/SKILL.md +88 -1
  55. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/customize.toml +41 -0
  56. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/steps/step-04-final-validation.md +6 -0
  57. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/SKILL.md +76 -1
  58. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/customize.toml +41 -0
  59. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/steps/step-03-complete.md +6 -0
  60. package/src/bmm-skills/4-implementation/bmad-agent-dev/SKILL.md +48 -43
  61. package/src/bmm-skills/4-implementation/bmad-agent-dev/customize.toml +90 -0
  62. package/src/bmm-skills/4-implementation/bmad-correct-course/SKILL.md +296 -1
  63. package/src/bmm-skills/4-implementation/bmad-correct-course/customize.toml +41 -0
  64. package/src/bmm-skills/4-implementation/bmad-create-story/SKILL.md +412 -1
  65. package/src/bmm-skills/4-implementation/bmad-create-story/customize.toml +41 -0
  66. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/SKILL.md +171 -1
  67. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/customize.toml +41 -0
  68. package/src/bmm-skills/4-implementation/bmad-retrospective/SKILL.md +1507 -1
  69. package/src/bmm-skills/4-implementation/bmad-retrospective/customize.toml +41 -0
  70. package/src/bmm-skills/module.yaml +49 -0
  71. package/src/core-skills/bmad-advanced-elicitation/SKILL.md +7 -1
  72. package/src/core-skills/bmad-customize/SKILL.md +111 -0
  73. package/src/core-skills/bmad-customize/scripts/list_customizable_skills.py +231 -0
  74. package/src/core-skills/bmad-customize/scripts/tests/test_list_customizable_skills.py +249 -0
  75. package/src/core-skills/bmad-distillator/resources/distillate-format-reference.md +1 -1
  76. package/src/core-skills/bmad-party-mode/SKILL.md +13 -10
  77. package/src/core-skills/module-help.csv +1 -0
  78. package/src/core-skills/module.yaml +3 -0
  79. package/src/scripts/resolve_config.py +176 -0
  80. package/src/scripts/resolve_customization.py +230 -0
  81. package/tools/installer/cli-utils.js +0 -137
  82. package/tools/installer/commands/install.js +13 -0
  83. package/tools/installer/commands/status.js +1 -1
  84. package/tools/installer/commands/uninstall.js +1 -1
  85. package/tools/installer/core/config.js +4 -1
  86. package/tools/installer/core/existing-install.js +1 -1
  87. package/tools/installer/core/install-paths.js +12 -6
  88. package/tools/installer/core/installer.js +182 -95
  89. package/tools/installer/core/manifest-generator.js +347 -190
  90. package/tools/installer/core/manifest.js +49 -642
  91. package/tools/installer/file-ops.js +1 -1
  92. package/tools/installer/fs-native.js +116 -0
  93. package/tools/installer/ide/_config-driven.js +1 -1
  94. package/tools/installer/ide/platform-codes.js +1 -1
  95. package/tools/installer/ide/shared/path-utils.js +0 -145
  96. package/tools/installer/ide/shared/skill-manifest.js +1 -1
  97. package/tools/installer/message-loader.js +1 -1
  98. package/tools/installer/modules/channel-plan.js +203 -0
  99. package/tools/installer/modules/channel-resolver.js +241 -0
  100. package/tools/installer/modules/community-manager.js +131 -24
  101. package/tools/installer/modules/custom-module-manager.js +161 -47
  102. package/tools/installer/modules/external-manager.js +236 -73
  103. package/tools/installer/modules/official-modules.js +61 -63
  104. package/tools/installer/modules/plugin-resolver.js +1 -1
  105. package/tools/installer/modules/registry-client.js +133 -12
  106. package/tools/installer/modules/registry-fallback.yaml +8 -0
  107. package/tools/installer/modules/version-resolver.js +336 -0
  108. package/tools/installer/project-root.js +55 -1
  109. package/tools/installer/prompts.js +0 -106
  110. package/tools/installer/ui.js +457 -51
  111. package/tools/migrate-custom-module-paths.js +1 -1
  112. package/src/bmm-skills/1-analysis/bmad-agent-analyst/bmad-skill-manifest.yaml +0 -11
  113. package/src/bmm-skills/1-analysis/bmad-agent-tech-writer/bmad-skill-manifest.yaml +0 -11
  114. package/src/bmm-skills/1-analysis/bmad-document-project/workflow.md +0 -25
  115. package/src/bmm-skills/1-analysis/research/bmad-domain-research/workflow.md +0 -51
  116. package/src/bmm-skills/1-analysis/research/bmad-market-research/workflow.md +0 -51
  117. package/src/bmm-skills/1-analysis/research/bmad-technical-research/workflow.md +0 -52
  118. package/src/bmm-skills/2-plan-workflows/bmad-agent-pm/bmad-skill-manifest.yaml +0 -11
  119. package/src/bmm-skills/2-plan-workflows/bmad-agent-ux-designer/bmad-skill-manifest.yaml +0 -11
  120. package/src/bmm-skills/2-plan-workflows/bmad-create-prd/workflow.md +0 -61
  121. package/src/bmm-skills/2-plan-workflows/bmad-create-ux-design/workflow.md +0 -35
  122. package/src/bmm-skills/2-plan-workflows/bmad-edit-prd/workflow.md +0 -62
  123. package/src/bmm-skills/2-plan-workflows/bmad-validate-prd/workflow.md +0 -61
  124. package/src/bmm-skills/3-solutioning/bmad-agent-architect/bmad-skill-manifest.yaml +0 -11
  125. package/src/bmm-skills/3-solutioning/bmad-check-implementation-readiness/workflow.md +0 -47
  126. package/src/bmm-skills/3-solutioning/bmad-create-architecture/workflow.md +0 -32
  127. package/src/bmm-skills/3-solutioning/bmad-create-epics-and-stories/workflow.md +0 -51
  128. package/src/bmm-skills/3-solutioning/bmad-generate-project-context/workflow.md +0 -39
  129. package/src/bmm-skills/4-implementation/bmad-agent-dev/bmad-skill-manifest.yaml +0 -11
  130. package/src/bmm-skills/4-implementation/bmad-correct-course/workflow.md +0 -267
  131. package/src/bmm-skills/4-implementation/bmad-create-story/workflow.md +0 -380
  132. package/src/bmm-skills/4-implementation/bmad-qa-generate-e2e-tests/workflow.md +0 -136
  133. package/src/bmm-skills/4-implementation/bmad-retrospective/workflow.md +0 -1479
  134. package/tools/installer/ide/shared/agent-command-generator.js +0 -180
  135. package/tools/installer/ide/shared/bmad-artifacts.js +0 -208
  136. package/tools/installer/ide/shared/module-injections.js +0 -136
  137. package/tools/installer/ide/templates/agent-command-template.md +0 -14
  138. package/tools/installer/ide/templates/combined/antigravity.md +0 -8
  139. package/tools/installer/ide/templates/combined/default-agent.md +0 -15
  140. package/tools/installer/ide/templates/combined/default-task.md +0 -10
  141. package/tools/installer/ide/templates/combined/default-tool.md +0 -10
  142. package/tools/installer/ide/templates/combined/default-workflow.md +0 -6
  143. package/tools/installer/ide/templates/combined/gemini-agent.toml +0 -14
  144. package/tools/installer/ide/templates/combined/gemini-task.toml +0 -11
  145. package/tools/installer/ide/templates/combined/gemini-tool.toml +0 -11
  146. package/tools/installer/ide/templates/combined/gemini-workflow-yaml.toml +0 -16
  147. package/tools/installer/ide/templates/combined/gemini-workflow.toml +0 -14
  148. package/tools/installer/ide/templates/combined/kiro-agent.md +0 -16
  149. package/tools/installer/ide/templates/combined/kiro-task.md +0 -9
  150. package/tools/installer/ide/templates/combined/kiro-tool.md +0 -9
  151. package/tools/installer/ide/templates/combined/kiro-workflow.md +0 -7
  152. package/tools/installer/ide/templates/combined/opencode-agent.md +0 -15
  153. package/tools/installer/ide/templates/combined/opencode-task.md +0 -13
  154. package/tools/installer/ide/templates/combined/opencode-tool.md +0 -13
  155. package/tools/installer/ide/templates/combined/opencode-workflow-yaml.md +0 -16
  156. package/tools/installer/ide/templates/combined/opencode-workflow.md +0 -16
  157. package/tools/installer/ide/templates/combined/rovodev.md +0 -9
  158. package/tools/installer/ide/templates/combined/trae.md +0 -9
  159. package/tools/installer/ide/templates/combined/windsurf-workflow.md +0 -10
  160. package/tools/installer/ide/templates/split/.gitkeep +0 -0
@@ -1,50 +1,20 @@
1
1
  const path = require('node:path');
2
2
  const os = require('node:os');
3
- const fs = require('fs-extra');
3
+ const fs = require('./fs-native');
4
4
  const { CLIUtils } = require('./cli-utils');
5
5
  const { ExternalModuleManager } = require('./modules/external-manager');
6
- const { getProjectRoot } = require('./project-root');
6
+ const { resolveModuleVersion } = require('./modules/version-resolver');
7
+ const { parseChannelOptions, buildPlan, orphanPinWarnings, bundledTargetWarnings } = require('./modules/channel-plan');
7
8
  const prompts = require('./prompts');
8
9
 
9
10
  /**
10
- * Read module version from .claude-plugin/marketplace.json
11
+ * Read a module version from the freshest local metadata available.
11
12
  * @param {string} moduleCode - Module code (e.g., 'core', 'bmm', 'cis')
12
13
  * @returns {string} Version string or empty string
13
14
  */
14
- async function getMarketplaceVersion(moduleCode) {
15
- let marketplacePath;
16
- if (moduleCode === 'core' || moduleCode === 'bmm') {
17
- marketplacePath = path.join(getProjectRoot(), '.claude-plugin', 'marketplace.json');
18
- } else {
19
- const cacheDir = path.join(os.homedir(), '.bmad', 'cache', 'external-modules', moduleCode);
20
- marketplacePath = path.join(cacheDir, '.claude-plugin', 'marketplace.json');
21
- }
22
- try {
23
- if (await fs.pathExists(marketplacePath)) {
24
- const data = JSON.parse(await fs.readFile(marketplacePath, 'utf8'));
25
- return _extractMarketplaceVersion(data);
26
- }
27
- } catch {
28
- // ignore
29
- }
30
- return '';
31
- }
32
-
33
- /**
34
- * Extract the highest version from marketplace.json plugins array.
35
- * Handles multiple plugins per file safely.
36
- * @param {Object} data - Parsed marketplace.json
37
- * @returns {string} Version string or empty string
38
- */
39
- function _extractMarketplaceVersion(data) {
40
- const plugins = data?.plugins;
41
- if (!Array.isArray(plugins) || plugins.length === 0) return '';
42
- // Use the highest version across all plugins in the file
43
- let best = '';
44
- for (const p of plugins) {
45
- if (p.version && (!best || p.version > best)) best = p.version;
46
- }
47
- return best;
15
+ async function getModuleVersion(moduleCode) {
16
+ const versionInfo = await resolveModuleVersion(moduleCode);
17
+ return versionInfo.version || '';
48
18
  }
49
19
 
50
20
  /**
@@ -64,6 +34,13 @@ class UI {
64
34
  const messageLoader = new MessageLoader();
65
35
  await messageLoader.displayStartMessage();
66
36
 
37
+ // Parse channel flags (--channel/--all-*/--next=/--pin) once. Warnings
38
+ // are surfaced immediately so the user sees them before any git ops run.
39
+ const channelOptions = parseChannelOptions(options);
40
+ for (const warning of channelOptions.warnings) {
41
+ await prompts.log.warn(warning);
42
+ }
43
+
67
44
  // Get directory from options or prompt
68
45
  let confirmedDirectory;
69
46
  if (options.directory) {
@@ -183,10 +160,38 @@ class UI {
183
160
  selectedModules.unshift('core');
184
161
  }
185
162
 
163
+ // For existing installs, resolve per-module update decisions BEFORE
164
+ // we clone anything. Reads the existing manifest's recorded channel
165
+ // per module and prompts the user on available upgrades (patch/minor
166
+ // default Y, major default N). Legacy entries with no channel are
167
+ // migrated here too. Mutates channelOptions.pins to lock rejections.
168
+ await this._resolveUpdateChannels({
169
+ bmadDir,
170
+ selectedModules,
171
+ channelOptions,
172
+ yes: options.yes || false,
173
+ });
174
+
186
175
  // Get tool selection
187
176
  const toolSelection = await this.promptToolSelection(confirmedDirectory, options);
188
177
 
189
- const moduleConfigs = await this.collectModuleConfigs(confirmedDirectory, selectedModules, options);
178
+ const moduleConfigs = await this.collectModuleConfigs(confirmedDirectory, selectedModules, {
179
+ ...options,
180
+ channelOptions,
181
+ });
182
+
183
+ // Warn about --pin/--next flags that refer to modules the user didn't
184
+ // select, or that target bundled modules (core/bmm) where channel
185
+ // flags don't apply.
186
+ {
187
+ const bundledCodes = await this._bundledModuleCodes();
188
+ for (const warning of [
189
+ ...orphanPinWarnings(channelOptions, selectedModules),
190
+ ...bundledTargetWarnings(channelOptions, bundledCodes),
191
+ ]) {
192
+ await prompts.log.warn(warning);
193
+ }
194
+ }
190
195
 
191
196
  return {
192
197
  actionType: 'update',
@@ -197,6 +202,7 @@ class UI {
197
202
  coreConfig: moduleConfigs.core || {},
198
203
  moduleConfigs: moduleConfigs,
199
204
  skipPrompts: options.yes || false,
205
+ channelOptions,
200
206
  };
201
207
  }
202
208
  }
@@ -236,8 +242,31 @@ class UI {
236
242
  if (!selectedModules.includes('core')) {
237
243
  selectedModules.unshift('core');
238
244
  }
245
+
246
+ // Interactive channel gate: "Ready to install (all stable)? [Y/n]"
247
+ // Only shown for fresh installs with no channel flags and an external module
248
+ // selected. Non-interactive installs skip this and fall through to the
249
+ // registry default (stable) or whatever flags were supplied.
250
+ await this._interactiveChannelGate({ options, channelOptions, selectedModules });
251
+
239
252
  let toolSelection = await this.promptToolSelection(confirmedDirectory, options);
240
- const moduleConfigs = await this.collectModuleConfigs(confirmedDirectory, selectedModules, options);
253
+ const moduleConfigs = await this.collectModuleConfigs(confirmedDirectory, selectedModules, {
254
+ ...options,
255
+ channelOptions,
256
+ });
257
+
258
+ // Warn about --pin/--next flags that refer to modules the user didn't
259
+ // select, or that target bundled modules (core/bmm) where channel
260
+ // flags don't apply.
261
+ {
262
+ const bundledCodes = await this._bundledModuleCodes();
263
+ for (const warning of [
264
+ ...orphanPinWarnings(channelOptions, selectedModules),
265
+ ...bundledTargetWarnings(channelOptions, bundledCodes),
266
+ ]) {
267
+ await prompts.log.warn(warning);
268
+ }
269
+ }
241
270
 
242
271
  return {
243
272
  actionType: 'install',
@@ -248,6 +277,7 @@ class UI {
248
277
  coreConfig: moduleConfigs.core || {},
249
278
  moduleConfigs: moduleConfigs,
250
279
  skipPrompts: options.yes || false,
280
+ channelOptions,
251
281
  };
252
282
  }
253
283
 
@@ -519,7 +549,7 @@ class UI {
519
549
  */
520
550
  async collectModuleConfigs(directory, modules, options = {}) {
521
551
  const { OfficialModules } = require('./modules/official-modules');
522
- const configCollector = new OfficialModules();
552
+ const configCollector = new OfficialModules({ channelOptions: options.channelOptions });
523
553
 
524
554
  // Seed core config from CLI options if provided
525
555
  if (options.userName || options.communicationLanguage || options.documentOutputLanguage || options.outputFolder) {
@@ -598,7 +628,7 @@ class UI {
598
628
  const officialCodes = new Set(officialSelected);
599
629
  const externalManager = new ExternalModuleManager();
600
630
  const registryModules = await externalManager.listAvailable();
601
- const officialRegistryCodes = new Set(registryModules.map((m) => m.code));
631
+ const officialRegistryCodes = new Set(['core', 'bmm', ...registryModules.map((m) => m.code)]);
602
632
  const installedNonOfficial = [...installedModuleIds].filter((id) => !officialRegistryCodes.has(id));
603
633
 
604
634
  // Phase 2: Community modules (category drill-down)
@@ -630,6 +660,11 @@ class UI {
630
660
  * @returns {Array} Selected official module codes
631
661
  */
632
662
  async _selectOfficialModules(installedModuleIds = new Set()) {
663
+ // Built-in modules (core, bmm) come from local source, not the registry
664
+ const { OfficialModules } = require('./modules/official-modules');
665
+ const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
666
+
667
+ // External modules come from the registry (with fallback)
633
668
  const externalManager = new ExternalModuleManager();
634
669
  const registryModules = await externalManager.listAvailable();
635
670
 
@@ -637,20 +672,34 @@ class UI {
637
672
  const initialValues = [];
638
673
  const lockedValues = ['core'];
639
674
 
640
- const buildModuleEntry = async (mod) => {
641
- const isInstalled = installedModuleIds.has(mod.code);
642
- const version = await getMarketplaceVersion(mod.code);
643
- const label = version ? `${mod.name} (v${version})` : mod.name;
675
+ const buildModuleEntry = async (code, name, description, isDefault) => {
676
+ const isInstalled = installedModuleIds.has(code);
677
+ const version = await getModuleVersion(code);
678
+ const label = version ? `${name} (v${version})` : name;
644
679
  return {
645
680
  label,
646
- value: mod.code,
647
- hint: mod.description,
648
- selected: isInstalled,
681
+ value: code,
682
+ hint: description,
683
+ selected: isInstalled || isDefault,
649
684
  };
650
685
  };
651
686
 
687
+ // Add built-in modules first (always available regardless of network)
688
+ const builtInCodes = new Set();
689
+ for (const mod of builtInModules) {
690
+ const code = mod.id;
691
+ builtInCodes.add(code);
692
+ const entry = await buildModuleEntry(code, mod.name, mod.description, mod.defaultSelected);
693
+ allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
694
+ if (entry.selected) {
695
+ initialValues.push(code);
696
+ }
697
+ }
698
+
699
+ // Add external registry modules (skip built-in duplicates)
652
700
  for (const mod of registryModules) {
653
- const entry = await buildModuleEntry(mod);
701
+ if (mod.builtIn || builtInCodes.has(mod.code)) continue;
702
+ const entry = await buildModuleEntry(mod.code, mod.name, mod.description, mod.defaultSelected);
654
703
  allOptions.push({ label: entry.label, value: entry.value, hint: entry.hint });
655
704
  if (entry.selected) {
656
705
  initialValues.push(mod.code);
@@ -1122,12 +1171,26 @@ class UI {
1122
1171
  * @returns {Array} Default module codes
1123
1172
  */
1124
1173
  async getDefaultModules(installedModuleIds = new Set()) {
1125
- const externalManager = new ExternalModuleManager();
1126
- const registryModules = await externalManager.listAvailable();
1174
+ // Built-in modules with default_selected come from local source
1175
+ const { OfficialModules } = require('./modules/official-modules');
1176
+ const builtInModules = (await new OfficialModules().listAvailable()).modules || [];
1127
1177
 
1128
1178
  const defaultModules = [];
1179
+ const seen = new Set();
1180
+
1181
+ for (const mod of builtInModules) {
1182
+ if (mod.defaultSelected || installedModuleIds.has(mod.id)) {
1183
+ defaultModules.push(mod.id);
1184
+ seen.add(mod.id);
1185
+ }
1186
+ }
1187
+
1188
+ // Add external registry defaults
1189
+ const externalManager = new ExternalModuleManager();
1190
+ const registryModules = await externalManager.listAvailable();
1129
1191
 
1130
1192
  for (const mod of registryModules) {
1193
+ if (mod.builtIn || seen.has(mod.code)) continue;
1131
1194
  if (mod.defaultSelected || installedModuleIds.has(mod.code)) {
1132
1195
  defaultModules.push(mod.code);
1133
1196
  }
@@ -1561,6 +1624,349 @@ class UI {
1561
1624
  });
1562
1625
  await prompts.log.message('Selected tools:\n' + toolLines.join('\n'));
1563
1626
  }
1627
+
1628
+ /**
1629
+ * Return the set of module codes the registry marks as built-in (core, bmm).
1630
+ * These ship with the installer binary and have no per-module channel.
1631
+ */
1632
+ async _bundledModuleCodes() {
1633
+ const externalManager = new ExternalModuleManager();
1634
+ try {
1635
+ const modules = await externalManager.listAvailable();
1636
+ return modules.filter((m) => m.builtIn).map((m) => m.code);
1637
+ } catch {
1638
+ // Registry unreachable — fall back to the known bundled codes.
1639
+ return ['core', 'bmm'];
1640
+ }
1641
+ }
1642
+
1643
+ /**
1644
+ * Fast-path channel gate: confirm "all stable" or open the per-module picker.
1645
+ *
1646
+ * Skipped when:
1647
+ * - running non-interactively (--yes)
1648
+ * - the user already passed channel flags (--channel / --pin / --next)
1649
+ * - no externals/community modules are selected
1650
+ *
1651
+ * Mutates channelOptions.pins and channelOptions.nextSet to reflect picker choices.
1652
+ */
1653
+ async _interactiveChannelGate({ options, channelOptions, selectedModules }) {
1654
+ if (options.yes) return;
1655
+ // If the user already declared their channel intent via flags, trust them
1656
+ // and skip the gate.
1657
+ const haveFlagIntent = channelOptions.global || channelOptions.nextSet.size > 0 || channelOptions.pins.size > 0;
1658
+ if (haveFlagIntent) return;
1659
+
1660
+ // Figure out which selected modules actually get a channel (externals +
1661
+ // community modules). Bundled core/bmm and custom modules skip the picker.
1662
+ const externalManager = new ExternalModuleManager();
1663
+ const externals = await externalManager.listAvailable();
1664
+ const externalByCode = new Map(externals.map((m) => [m.code, m]));
1665
+
1666
+ const { CommunityModuleManager } = require('./modules/community-manager');
1667
+ const communityMgr = new CommunityModuleManager();
1668
+ const community = await communityMgr.listAll();
1669
+ const communityByCode = new Map(community.map((m) => [m.code, m]));
1670
+
1671
+ const channelSelectable = selectedModules.filter((code) => {
1672
+ const info = externalByCode.get(code) || communityByCode.get(code);
1673
+ return info && !info.builtIn;
1674
+ });
1675
+ if (channelSelectable.length === 0) return;
1676
+
1677
+ const fastPath = await prompts.confirm({
1678
+ message: `Ready to install (all stable)? Pick "n" to customize channels or pin versions.`,
1679
+ default: true,
1680
+ });
1681
+ if (fastPath) return; // stable for all, registry default applies
1682
+
1683
+ // Customize path: per-module picker.
1684
+ const { fetchStableTags, parseGitHubRepo } = require('./modules/channel-resolver');
1685
+
1686
+ for (const code of channelSelectable) {
1687
+ const info = externalByCode.get(code) || communityByCode.get(code);
1688
+ const repoUrl = info.url;
1689
+
1690
+ // Try to pre-resolve the top stable tag so we can surface it in the picker.
1691
+ let stableLabel = 'stable (released version)';
1692
+ try {
1693
+ const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
1694
+ if (parsed) {
1695
+ const tags = await fetchStableTags(parsed.owner, parsed.repo);
1696
+ if (tags.length > 0) {
1697
+ stableLabel = `stable ${tags[0].tag} (released version)`;
1698
+ }
1699
+ }
1700
+ } catch {
1701
+ // fall through with the generic label
1702
+ }
1703
+
1704
+ const choice = await prompts.select({
1705
+ message: `${code}: choose a channel`,
1706
+ choices: [
1707
+ { name: stableLabel, value: 'stable' },
1708
+ { name: 'next (main HEAD \u2014 current development)', value: 'next' },
1709
+ { name: 'pin (specific version)', value: 'pin' },
1710
+ ],
1711
+ default: 'stable',
1712
+ });
1713
+
1714
+ if (choice === 'next') {
1715
+ channelOptions.nextSet.add(code);
1716
+ } else if (choice === 'pin') {
1717
+ const pinValue = await prompts.text({
1718
+ message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
1719
+ validate: (value) => {
1720
+ if (!value || !/^[\w.\-+/]+$/.test(String(value).trim())) {
1721
+ return 'Must be a non-empty tag name (letters, digits, dots, hyphens).';
1722
+ }
1723
+ },
1724
+ });
1725
+ channelOptions.pins.set(code, String(pinValue).trim());
1726
+ }
1727
+ // 'stable' is the default; nothing to record.
1728
+ }
1729
+ }
1730
+
1731
+ /**
1732
+ * Resolve channel decisions for an update over an existing install.
1733
+ *
1734
+ * For each selected external/community module:
1735
+ * - Read the recorded channel from the existing manifest.
1736
+ * - On `stable`: query tags; if a newer stable exists, classify the diff
1737
+ * and prompt. Patch/minor default Y; major defaults N. `--yes` accepts
1738
+ * defaults (patches/minors) but NOT majors — a major under --yes stays
1739
+ * frozen unless the user also passes `--pin CODE=NEW_TAG`.
1740
+ * - On `next`: no prompt (pull HEAD).
1741
+ * - On `pinned`: no prompt (stays pinned).
1742
+ * - No channel recorded and `version: null`: one-time migration prompt
1743
+ * ("Switch to stable / Keep on next").
1744
+ *
1745
+ * Decisions that freeze the current version are applied by adding a pin to
1746
+ * `channelOptions.pins` so downstream clone logic honors them.
1747
+ */
1748
+ async _resolveUpdateChannels({ bmadDir, selectedModules, channelOptions, yes }) {
1749
+ const { Manifest } = require('./core/manifest');
1750
+ const manifestObj = new Manifest();
1751
+ const manifest = await manifestObj.read(bmadDir);
1752
+ const existingByName = new Map();
1753
+ for (const m of manifest?.modulesDetailed || []) {
1754
+ if (m?.name) existingByName.set(m.name, m);
1755
+ }
1756
+ if (existingByName.size === 0) return;
1757
+
1758
+ const externalManager = new ExternalModuleManager();
1759
+ const externals = await externalManager.listAvailable();
1760
+ const externalByCode = new Map(externals.map((m) => [m.code, m]));
1761
+
1762
+ const { CommunityModuleManager } = require('./modules/community-manager');
1763
+ const communityMgr = new CommunityModuleManager();
1764
+ const community = await communityMgr.listAll();
1765
+ const communityByCode = new Map(community.map((m) => [m.code, m]));
1766
+
1767
+ const { fetchStableTags, classifyUpgrade, releaseNotesUrl } = require('./modules/channel-resolver');
1768
+ const { parseGitHubRepo } = require('./modules/channel-resolver');
1769
+
1770
+ // Interactive-only: offer a one-time gate to review / switch channels for
1771
+ // selected modules that are already installed. Default N so normal Modify
1772
+ // flows (add/remove modules) aren't interrupted.
1773
+ let reviewChannels = false;
1774
+ if (!yes) {
1775
+ const existingWithChannel = selectedModules.filter((code) => {
1776
+ const prev = existingByName.get(code);
1777
+ if (!prev) return false;
1778
+ const info = externalByCode.get(code) || communityByCode.get(code);
1779
+ return info && !info.builtIn;
1780
+ });
1781
+ if (existingWithChannel.length > 0) {
1782
+ reviewChannels = await prompts.confirm({
1783
+ message: 'Review channel assignments (stable / next / pin) for your existing modules?',
1784
+ default: false,
1785
+ });
1786
+ }
1787
+ }
1788
+
1789
+ for (const code of selectedModules) {
1790
+ const prev = existingByName.get(code);
1791
+ if (!prev) continue;
1792
+
1793
+ const info = externalByCode.get(code) || communityByCode.get(code);
1794
+ if (!info) continue;
1795
+ // Bundled modules (core/bmm) ship with the installer binary itself —
1796
+ // their version is stapled to the CLI version, not a git tag. Skip
1797
+ // tag-API lookups for them; the "upgrade" mechanism is `npx bmad@X install`.
1798
+ if (info.builtIn) continue;
1799
+
1800
+ const repoUrl = info.url;
1801
+ const parsed = repoUrl ? parseGitHubRepo(repoUrl) : null;
1802
+
1803
+ // Legacy migration: manifest carries no channel and a null/empty
1804
+ // version. Offer the one-time pick between stable and next.
1805
+ const recordedChannel = prev.channel || null;
1806
+ const needsMigration = !recordedChannel && (prev.version == null || prev.version === '');
1807
+ if (needsMigration) {
1808
+ if (yes) {
1809
+ // Conservative headless default: stable.
1810
+ continue;
1811
+ }
1812
+ const chosen = await prompts.select({
1813
+ message: `${code}: your existing install tracks the main branch. Switch to stable releases (recommended for production), or keep on main?`,
1814
+ choices: [
1815
+ { name: 'Switch to stable', value: 'stable' },
1816
+ { name: 'Keep on main (next)', value: 'next' },
1817
+ ],
1818
+ default: 'stable',
1819
+ });
1820
+ if (chosen === 'next') channelOptions.nextSet.add(code);
1821
+ continue;
1822
+ }
1823
+
1824
+ // Optional channel-switch offer. Fires only when the user opted in via
1825
+ // the gate above. 'keep' falls through to the existing per-channel
1826
+ // logic (which runs upgrade classification for stable). Any switch
1827
+ // records the new intent into channelOptions and skips upgrade prompts.
1828
+ if (reviewChannels && recordedChannel) {
1829
+ const switchChoices = [
1830
+ {
1831
+ name: `Keep on '${recordedChannel}'${prev.version ? ` @ ${prev.version}` : ''}`,
1832
+ value: 'keep',
1833
+ },
1834
+ ];
1835
+ if (recordedChannel !== 'stable') {
1836
+ switchChoices.push({ name: 'Switch to stable (released version)', value: 'stable' });
1837
+ }
1838
+ if (recordedChannel !== 'next') {
1839
+ switchChoices.push({ name: 'Switch to next (main HEAD)', value: 'next' });
1840
+ }
1841
+ switchChoices.push({ name: 'Pin to a specific version tag', value: 'pin' });
1842
+
1843
+ const choice = await prompts.select({
1844
+ message: `${code} channel:`,
1845
+ choices: switchChoices,
1846
+ default: 'keep',
1847
+ });
1848
+
1849
+ if (choice === 'next') {
1850
+ channelOptions.nextSet.add(code);
1851
+ continue;
1852
+ }
1853
+ if (choice === 'pin') {
1854
+ const pinValue = await prompts.text({
1855
+ message: `Enter a version tag for '${code}' (e.g. v1.6.0):`,
1856
+ validate: (value) => {
1857
+ if (!value || !/^[\w.\-+/]+$/.test(String(value).trim())) {
1858
+ return 'Must be a non-empty tag name (letters, digits, dots, hyphens).';
1859
+ }
1860
+ },
1861
+ });
1862
+ channelOptions.pins.set(code, String(pinValue).trim());
1863
+ continue;
1864
+ }
1865
+ if (choice === 'stable') {
1866
+ // Switch to stable: install at the top stable tag without an
1867
+ // upgrade-classification prompt (the user explicitly opted in).
1868
+ // Also warm the tag cache here so the actual clone step doesn't
1869
+ // need a second GitHub API call (can hit rate limits).
1870
+ if (parsed) {
1871
+ try {
1872
+ await fetchStableTags(parsed.owner, parsed.repo);
1873
+ } catch {
1874
+ // best effort; clone step will surface any failure
1875
+ }
1876
+ }
1877
+ continue;
1878
+ }
1879
+ // 'keep' → fall through with recordedChannel below.
1880
+ }
1881
+
1882
+ if (recordedChannel === 'pinned' || recordedChannel === 'next') {
1883
+ // Respect any explicit channel intent the user already expressed via
1884
+ // CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG) or
1885
+ // via the interactive review gate above. Only auto-re-assert the
1886
+ // recorded channel when the user hasn't opted into anything else —
1887
+ // otherwise --all-stable (or a review "switch to stable") would be
1888
+ // silently clobbered by the prior channel.
1889
+ const alreadyDecided = channelOptions.global || channelOptions.nextSet.has(code) || channelOptions.pins.has(code);
1890
+ if (!alreadyDecided) {
1891
+ if (recordedChannel === 'pinned' && prev.version) {
1892
+ channelOptions.pins.set(code, prev.version);
1893
+ } else if (recordedChannel === 'next') {
1894
+ channelOptions.nextSet.add(code);
1895
+ }
1896
+ }
1897
+ continue;
1898
+ }
1899
+
1900
+ // Stable channel: check for a newer released tag.
1901
+ if (!parsed) continue;
1902
+ // Respect explicit CLI intent (--pin / --next=CODE / --all-*) and any
1903
+ // choice the user already made in the earlier review gate. Without this
1904
+ // guard the upgrade classifier below would unconditionally call
1905
+ // `channelOptions.pins.set(code, prev.version)` on decline/major-refuse/
1906
+ // fetch-error, silently clobbering the user's override.
1907
+ const alreadyDecided = channelOptions.global || channelOptions.nextSet.has(code) || channelOptions.pins.has(code);
1908
+ if (alreadyDecided) continue;
1909
+ let tags;
1910
+ try {
1911
+ tags = await fetchStableTags(parsed.owner, parsed.repo);
1912
+ } catch (error) {
1913
+ await prompts.log.warn(`Could not check for updates on ${code} (${error.message}). Leaving at ${prev.version}.`);
1914
+ if (prev.version) channelOptions.pins.set(code, prev.version);
1915
+ continue;
1916
+ }
1917
+ if (!tags || tags.length === 0) continue;
1918
+ const topTag = tags[0].tag; // e.g. "v1.7.0"
1919
+ const currentTag = prev.version || '';
1920
+ const diffClass = classifyUpgrade(currentTag, topTag);
1921
+
1922
+ if (diffClass === 'none') continue; // already at or above top tag
1923
+
1924
+ const notes = releaseNotesUrl(repoUrl, topTag);
1925
+ let accept;
1926
+ if (diffClass === 'major') {
1927
+ if (yes) {
1928
+ // Major under --yes is refused by design.
1929
+ await prompts.log.warn(
1930
+ `${code} ${currentTag} → ${topTag} is a new major release; staying on ${currentTag}. ` +
1931
+ `To accept, rerun with --pin ${code}=${topTag}.`,
1932
+ );
1933
+ channelOptions.pins.set(code, currentTag);
1934
+ continue;
1935
+ }
1936
+ accept = await prompts.confirm({
1937
+ message:
1938
+ `${code} ${topTag} available — new major release (may change behavior).` +
1939
+ (notes ? ` Release notes: ${notes}.` : '') +
1940
+ ' Upgrade?',
1941
+ default: false,
1942
+ });
1943
+ } else if (diffClass === 'minor') {
1944
+ if (yes) {
1945
+ accept = true;
1946
+ } else {
1947
+ accept = await prompts.confirm({
1948
+ message: `${code} ${topTag} available (new features).` + (notes ? ` Release notes: ${notes}.` : '') + ' Upgrade?',
1949
+ default: true,
1950
+ });
1951
+ }
1952
+ } else {
1953
+ // patch
1954
+ if (yes) {
1955
+ accept = true;
1956
+ } else {
1957
+ accept = await prompts.confirm({
1958
+ message: `${code} ${topTag} available. Upgrade?`,
1959
+ default: true,
1960
+ });
1961
+ }
1962
+ }
1963
+
1964
+ if (!accept && currentTag) {
1965
+ // Freeze the current version by pinning it for this run.
1966
+ channelOptions.pins.set(code, currentTag);
1967
+ }
1968
+ }
1969
+ }
1564
1970
  }
1565
1971
 
1566
1972
  module.exports = { UI };
@@ -3,7 +3,7 @@
3
3
  * This should be run once to update existing installations
4
4
  */
5
5
 
6
- const fs = require('fs-extra');
6
+ const fs = require('./installer/fs-native');
7
7
  const path = require('node:path');
8
8
  const yaml = require('yaml');
9
9
  const chalk = require('chalk');
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-analyst
3
- displayName: Mary
4
- title: Business Analyst
5
- icon: "📊"
6
- capabilities: "market research, competitive analysis, requirements elicitation, domain expertise"
7
- role: Strategic Business Analyst + Requirements Expert
8
- identity: "Senior analyst with deep expertise in market research, competitive analysis, and requirements elicitation. Specializes in translating vague needs into actionable specs."
9
- communicationStyle: "Speaks with the excitement of a treasure hunter - thrilled by every clue, energized when patterns emerge. Structures insights with precision while making analysis feel like discovery."
10
- principles: "Channel expert business analysis frameworks: draw upon Porter's Five Forces, SWOT analysis, root cause analysis, and competitive intelligence methodologies to uncover what others miss. Every business challenge has root causes waiting to be discovered. Ground findings in verifiable evidence. Articulate requirements with absolute precision. Ensure all stakeholder voices heard."
11
- module: bmm
@@ -1,11 +0,0 @@
1
- type: agent
2
- name: bmad-agent-tech-writer
3
- displayName: Paige
4
- title: Technical Writer
5
- icon: "📚"
6
- capabilities: "documentation, Mermaid diagrams, standards compliance, concept explanation"
7
- role: Technical Documentation Specialist + Knowledge Curator
8
- identity: "Experienced technical writer expert in CommonMark, DITA, OpenAPI. Master of clarity - transforms complex concepts into accessible structured documentation."
9
- communicationStyle: "Patient educator who explains like teaching a friend. Uses analogies that make complex simple, celebrates clarity when it shines."
10
- principles: "Every Technical Document I touch helps someone accomplish a task. Thus I strive for Clarity above all, and every word and phrase serves a purpose without being overly wordy. I believe a picture/diagram is worth 1000s of words and will include diagrams over drawn out text. I understand the intended audience or will clarify with the user so I know when to simplify vs when to be detailed."
11
- module: bmm
@@ -1,25 +0,0 @@
1
- # Document Project Workflow
2
-
3
- **Goal:** Document brownfield projects for AI context.
4
-
5
- **Your Role:** Project documentation specialist.
6
- - Communicate all responses in {communication_language}
7
-
8
- ---
9
-
10
- ## INITIALIZATION
11
-
12
- 1. Load config from `{project-root}/_bmad/bmm/config.yaml` and resolve::
13
- - Use `{user_name}` for greeting
14
- - Use `{communication_language}` for all communications
15
- - Use `{document_output_language}` for output documents
16
- - Use `{planning_artifacts}` for output location and artifact scanning
17
- - Use `{project_knowledge}` for additional context scanning
18
-
19
- 2. **Greet user** as `{user_name}`, speaking in `{communication_language}`.
20
-
21
- ---
22
-
23
- ## EXECUTION
24
-
25
- Read fully and follow: `./instructions.md`