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,4 +1,4 @@
1
- const fs = require('fs-extra');
1
+ const fs = require('./fs-native');
2
2
  const path = require('node:path');
3
3
  const crypto = require('node:crypto');
4
4
 
@@ -0,0 +1,116 @@
1
+ // Drop-in replacement for fs-extra using native node:fs APIs.
2
+ // Eliminates graceful-fs monkey-patching that causes non-deterministic
3
+ // file loss during multi-module installs on macOS (issue #1779).
4
+ const fsp = require('node:fs/promises');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ async function pathExists(p) {
9
+ try {
10
+ await fsp.access(p);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
15
+ }
16
+
17
+ async function ensureDir(dir) {
18
+ await fsp.mkdir(dir, { recursive: true });
19
+ }
20
+
21
+ async function remove(p) {
22
+ await fsp.rm(p, { recursive: true, force: true });
23
+ }
24
+
25
+ async function copy(src, dest, options = {}) {
26
+ const filterFn = options.filter;
27
+ const overwrite = options.overwrite !== false;
28
+ const srcStat = await fsp.stat(src);
29
+
30
+ if (srcStat.isFile()) {
31
+ if (filterFn && !(await filterFn(src, dest))) return;
32
+ await fsp.mkdir(path.dirname(dest), { recursive: true });
33
+ if (!overwrite) {
34
+ try {
35
+ await fsp.access(dest);
36
+ if (options.errorOnExist) throw new Error(`${dest} already exists`);
37
+ return;
38
+ } catch (error) {
39
+ if (error.message.includes('already exists')) throw error;
40
+ }
41
+ }
42
+ await fsp.copyFile(src, dest);
43
+ return;
44
+ }
45
+
46
+ if (srcStat.isDirectory()) {
47
+ if (filterFn && !(await filterFn(src, dest))) return;
48
+ await fsp.mkdir(dest, { recursive: true });
49
+ const entries = await fsp.readdir(src, { withFileTypes: true });
50
+ for (const entry of entries) {
51
+ await copy(path.join(src, entry.name), path.join(dest, entry.name), options);
52
+ }
53
+ }
54
+ }
55
+
56
+ async function move(src, dest) {
57
+ try {
58
+ await fsp.rename(src, dest);
59
+ } catch (error) {
60
+ if (error.code === 'EXDEV') {
61
+ await copy(src, dest);
62
+ await fsp.rm(src, { recursive: true, force: true });
63
+ } else {
64
+ throw error;
65
+ }
66
+ }
67
+ }
68
+
69
+ function readJsonSync(p) {
70
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
71
+ }
72
+
73
+ async function writeJson(p, data, options = {}) {
74
+ const spaces = options.spaces ?? 2;
75
+ await fsp.writeFile(p, JSON.stringify(data, null, spaces) + '\n', 'utf8');
76
+ }
77
+
78
+ module.exports = {
79
+ // Native async (node:fs/promises)
80
+ readFile: fsp.readFile,
81
+ writeFile: fsp.writeFile,
82
+ stat: fsp.stat,
83
+ readdir: fsp.readdir,
84
+ access: fsp.access,
85
+ realpath: fsp.realpath,
86
+ rename: fsp.rename,
87
+ rmdir: fsp.rmdir,
88
+ unlink: fsp.unlink,
89
+ chmod: fsp.chmod,
90
+ mkdir: fsp.mkdir,
91
+ mkdtemp: fsp.mkdtemp,
92
+ copyFile: fsp.copyFile,
93
+ rm: fsp.rm,
94
+
95
+ // fs-extra compatible helpers (native implementations)
96
+ pathExists,
97
+ ensureDir,
98
+ remove,
99
+ copy,
100
+ move,
101
+ readJsonSync,
102
+ writeJson,
103
+
104
+ // Sync methods from core node:fs
105
+ existsSync: fs.existsSync.bind(fs),
106
+ readFileSync: fs.readFileSync.bind(fs),
107
+ writeFileSync: fs.writeFileSync.bind(fs),
108
+ statSync: fs.statSync.bind(fs),
109
+ accessSync: fs.accessSync.bind(fs),
110
+ readdirSync: fs.readdirSync.bind(fs),
111
+ createReadStream: fs.createReadStream.bind(fs),
112
+ pathExistsSync: fs.existsSync.bind(fs),
113
+
114
+ // Constants
115
+ constants: fs.constants,
116
+ };
@@ -1,6 +1,6 @@
1
1
  const os = require('node:os');
2
2
  const path = require('node:path');
3
- const fs = require('fs-extra');
3
+ const fs = require('../fs-native');
4
4
  const yaml = require('yaml');
5
5
  const prompts = require('../prompts');
6
6
  const csv = require('csv-parse/sync');
@@ -1,4 +1,4 @@
1
- const fs = require('fs-extra');
1
+ const fs = require('../fs-native');
2
2
  const path = require('node:path');
3
3
  const yaml = require('yaml');
4
4
 
@@ -15,8 +15,6 @@
15
15
  * - standalone/agents/fred.md → bmad-agent-standalone-fred.md
16
16
  */
17
17
 
18
- // Type segments - agents are included in naming, others are filtered out
19
- const TYPE_SEGMENTS = ['workflows', 'tasks', 'tools'];
20
18
  const AGENT_SEGMENT = 'agents';
21
19
 
22
20
  // BMAD installation folder name - centralized constant for all installers
@@ -194,125 +192,6 @@ function parseDashName(filename) {
194
192
  };
195
193
  }
196
194
 
197
- // ============================================================================
198
- // LEGACY FUNCTIONS (underscore format) - kept for backward compatibility
199
- // ============================================================================
200
-
201
- /**
202
- * Convert hierarchical path to flat underscore-separated name (LEGACY)
203
- * @deprecated Use toDashName instead
204
- */
205
- function toUnderscoreName(module, type, name) {
206
- const isAgent = type === AGENT_SEGMENT;
207
- if (module === 'core') {
208
- return isAgent ? `bmad_agent_${name}.md` : `bmad_${name}.md`;
209
- }
210
- if (module === 'standalone') {
211
- return isAgent ? `bmad_agent_standalone_${name}.md` : `bmad_standalone_${name}.md`;
212
- }
213
- return isAgent ? `bmad_${module}_agent_${name}.md` : `bmad_${module}_${name}.md`;
214
- }
215
-
216
- /**
217
- * Convert relative path to flat underscore-separated name (LEGACY)
218
- * @deprecated Use toDashPath instead
219
- */
220
- function toUnderscorePath(relativePath) {
221
- // Strip common file extensions (same as toDashPath for consistency)
222
- const withoutExt = relativePath.replace(/\.(md|yaml|yml|json|xml|toml)$/i, '');
223
- const parts = withoutExt.split(/[/\\]/);
224
-
225
- const module = parts[0];
226
- const type = parts[1];
227
- const name = parts.slice(2).join('_');
228
-
229
- return toUnderscoreName(module, type, name);
230
- }
231
-
232
- /**
233
- * Create custom agent underscore name (LEGACY)
234
- * @deprecated Use customAgentDashName instead
235
- */
236
- function customAgentUnderscoreName(agentName) {
237
- return `bmad_custom_${agentName}.md`;
238
- }
239
-
240
- /**
241
- * Check if a filename uses underscore format (LEGACY)
242
- * @deprecated Use isDashFormat instead
243
- */
244
- function isUnderscoreFormat(filename) {
245
- return filename.startsWith('bmad_') && filename.includes('_');
246
- }
247
-
248
- /**
249
- * Extract parts from an underscore-formatted filename (LEGACY)
250
- * @deprecated Use parseDashName instead
251
- */
252
- function parseUnderscoreName(filename) {
253
- const withoutExt = filename.replace('.md', '');
254
- const parts = withoutExt.split('_');
255
-
256
- if (parts.length < 2 || parts[0] !== 'bmad') {
257
- return null;
258
- }
259
-
260
- const agentIndex = parts.indexOf('agent');
261
-
262
- if (agentIndex !== -1) {
263
- if (agentIndex === 1) {
264
- // bmad_agent_... - check for standalone
265
- if (parts.length >= 4 && parts[2] === 'standalone') {
266
- return {
267
- prefix: parts[0],
268
- module: 'standalone',
269
- type: 'agents',
270
- name: parts.slice(3).join('_'),
271
- };
272
- }
273
- return {
274
- prefix: parts[0],
275
- module: 'core',
276
- type: 'agents',
277
- name: parts.slice(agentIndex + 1).join('_'),
278
- };
279
- } else {
280
- return {
281
- prefix: parts[0],
282
- module: parts[1],
283
- type: 'agents',
284
- name: parts.slice(agentIndex + 1).join('_'),
285
- };
286
- }
287
- }
288
-
289
- if (parts.length === 2) {
290
- return {
291
- prefix: parts[0],
292
- module: 'core',
293
- type: 'workflows',
294
- name: parts[1],
295
- };
296
- }
297
-
298
- // Check for standalone non-agent: bmad_standalone_name
299
- if (parts[1] === 'standalone') {
300
- return {
301
- prefix: parts[0],
302
- module: 'standalone',
303
- type: 'workflows',
304
- name: parts.slice(2).join('_'),
305
- };
306
- }
307
-
308
- return {
309
- prefix: parts[0],
310
- module: parts[1],
311
- type: 'workflows',
312
- name: parts.slice(2).join('_'),
313
- };
314
- }
315
-
316
195
  /**
317
196
  * Resolve the skill name for an artifact.
318
197
  * Prefers canonicalId from a bmad-skill-manifest.yaml sidecar when available,
@@ -328,37 +207,13 @@ function resolveSkillName(artifact) {
328
207
  return toDashPath(artifact.relativePath);
329
208
  }
330
209
 
331
- // Backward compatibility aliases (colon format was same as underscore)
332
- const toColonName = toUnderscoreName;
333
- const toColonPath = toUnderscorePath;
334
- const customAgentColonName = customAgentUnderscoreName;
335
- const isColonFormat = isUnderscoreFormat;
336
- const parseColonName = parseUnderscoreName;
337
-
338
210
  module.exports = {
339
- // New standard (dash-based)
340
211
  toDashName,
341
212
  toDashPath,
342
213
  resolveSkillName,
343
214
  customAgentDashName,
344
215
  isDashFormat,
345
216
  parseDashName,
346
-
347
- // Legacy (underscore-based) - kept for backward compatibility
348
- toUnderscoreName,
349
- toUnderscorePath,
350
- customAgentUnderscoreName,
351
- isUnderscoreFormat,
352
- parseUnderscoreName,
353
-
354
- // Backward compatibility aliases
355
- toColonName,
356
- toColonPath,
357
- customAgentColonName,
358
- isColonFormat,
359
- parseColonName,
360
-
361
- TYPE_SEGMENTS,
362
217
  AGENT_SEGMENT,
363
218
  BMAD_FOLDER_NAME,
364
219
  };
@@ -1,5 +1,5 @@
1
1
  const path = require('node:path');
2
- const fs = require('fs-extra');
2
+ const fs = require('../../fs-native');
3
3
  const yaml = require('yaml');
4
4
 
5
5
  /**
@@ -1,4 +1,4 @@
1
- const fs = require('fs-extra');
1
+ const fs = require('./fs-native');
2
2
  const path = require('node:path');
3
3
  const yaml = require('yaml');
4
4
  const prompts = require('./prompts');
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Channel plan: the per-module resolution decision applied at install time.
3
+ *
4
+ * A "plan entry" for a module is:
5
+ * { channel: 'stable'|'next'|'pinned', pin?: string }
6
+ *
7
+ * We build the plan from:
8
+ * 1. CLI flags (--channel / --all-* / --next=CODE / --pin CODE=TAG)
9
+ * 2. Interactive answers (the "all stable?" gate + per-module picker)
10
+ * 3. Registry defaults (default_channel from registry-fallback.yaml / official.yaml)
11
+ * 4. Hardcoded fallback 'stable'
12
+ *
13
+ * Precedence: --pin > --next=CODE > --channel (global) > registry default > 'stable'.
14
+ *
15
+ * This module is pure. No prompts, no git, no filesystem.
16
+ */
17
+
18
+ const VALID_CHANNELS = new Set(['stable', 'next']);
19
+
20
+ /**
21
+ * Parse raw commander options into a structured channel options object.
22
+ *
23
+ * @param {Object} options - raw command-line options
24
+ * @returns {{
25
+ * global: 'stable'|'next'|null,
26
+ * nextSet: Set<string>,
27
+ * pins: Map<string, string>,
28
+ * warnings: string[]
29
+ * }}
30
+ */
31
+ function parseChannelOptions(options = {}) {
32
+ const warnings = [];
33
+
34
+ // Global channel from --channel / --all-stable / --all-next.
35
+ let global = null;
36
+ const aliases = [];
37
+ if (options.channel) aliases.push({ flag: '--channel', value: normalizeChannel(options.channel, warnings, '--channel') });
38
+ if (options.allStable) aliases.push({ flag: '--all-stable', value: 'stable' });
39
+ if (options.allNext) aliases.push({ flag: '--all-next', value: 'next' });
40
+
41
+ const distinct = new Set(aliases.map((a) => a.value).filter(Boolean));
42
+ if (distinct.size > 1) {
43
+ warnings.push(
44
+ `Conflicting channel flags: ${aliases
45
+ .filter((a) => a.value)
46
+ .map((a) => a.flag + '=' + a.value)
47
+ .join(', ')}. Using first: ${aliases.find((a) => a.value).flag}.`,
48
+ );
49
+ }
50
+ const firstValid = aliases.find((a) => a.value);
51
+ if (firstValid) global = firstValid.value;
52
+
53
+ // --next=CODE (repeatable)
54
+ const nextSet = new Set();
55
+ for (const code of options.next || []) {
56
+ const trimmed = String(code).trim();
57
+ if (!trimmed) continue;
58
+ nextSet.add(trimmed);
59
+ }
60
+
61
+ // --pin CODE=TAG (repeatable)
62
+ const pins = new Map();
63
+ for (const spec of options.pin || []) {
64
+ const parsed = parsePinSpec(spec);
65
+ if (!parsed) {
66
+ warnings.push(`Ignoring malformed --pin value '${spec}'. Expected CODE=TAG.`);
67
+ continue;
68
+ }
69
+ if (pins.has(parsed.code)) {
70
+ warnings.push(`--pin specified multiple times for '${parsed.code}'. Using last: ${parsed.tag}.`);
71
+ }
72
+ pins.set(parsed.code, parsed.tag);
73
+ }
74
+
75
+ // --yes auto-confirms the community-module curator-bypass prompt so
76
+ // headless installs with --next=/--pin for a community module don't hang.
77
+ const acceptBypass = options.yes === true || options.acceptBypass === true;
78
+
79
+ return { global, nextSet, pins, warnings, acceptBypass };
80
+ }
81
+
82
+ function normalizeChannel(raw, warnings, flagName) {
83
+ if (typeof raw !== 'string') return null;
84
+ const lower = raw.trim().toLowerCase();
85
+ if (VALID_CHANNELS.has(lower)) return lower;
86
+ warnings.push(`Ignoring invalid ${flagName} value '${raw}'. Expected one of: stable, next.`);
87
+ return null;
88
+ }
89
+
90
+ function parsePinSpec(spec) {
91
+ if (typeof spec !== 'string') return null;
92
+ const idx = spec.indexOf('=');
93
+ if (idx <= 0 || idx === spec.length - 1) return null;
94
+ const code = spec.slice(0, idx).trim();
95
+ const tag = spec.slice(idx + 1).trim();
96
+ if (!code || !tag) return null;
97
+ return { code, tag };
98
+ }
99
+
100
+ /**
101
+ * Build a per-module plan entry, applying precedence.
102
+ *
103
+ * @param {Object} args
104
+ * @param {string} args.code
105
+ * @param {Object} args.channelOptions - from parseChannelOptions
106
+ * @param {string} [args.registryDefault] - module's default_channel, if any
107
+ * @returns {{channel: 'stable'|'next'|'pinned', pin?: string, source: string}}
108
+ * source describes where the decision came from, for logging / debugging.
109
+ */
110
+ function decideChannelForModule({ code, channelOptions, registryDefault }) {
111
+ const { global, nextSet, pins } = channelOptions || { nextSet: new Set(), pins: new Map() };
112
+
113
+ if (pins && pins.has(code)) {
114
+ return { channel: 'pinned', pin: pins.get(code), source: 'flag:--pin' };
115
+ }
116
+ if (nextSet && nextSet.has(code)) {
117
+ return { channel: 'next', source: 'flag:--next' };
118
+ }
119
+ if (global) {
120
+ return { channel: global, source: 'flag:--channel' };
121
+ }
122
+ if (registryDefault && VALID_CHANNELS.has(registryDefault)) {
123
+ return { channel: registryDefault, source: 'registry' };
124
+ }
125
+ return { channel: 'stable', source: 'default' };
126
+ }
127
+
128
+ /**
129
+ * Build a full channel plan map for a set of modules.
130
+ *
131
+ * @param {Object} args
132
+ * @param {Array<{code: string, defaultChannel?: string, builtIn?: boolean}>} args.modules
133
+ * Only the modules that need a channel entry; callers should filter out
134
+ * bundled modules (core/bmm) before calling.
135
+ * @param {Object} args.channelOptions - from parseChannelOptions
136
+ * @returns {Map<string, {channel: string, pin?: string, source: string}>}
137
+ */
138
+ function buildPlan({ modules, channelOptions }) {
139
+ const plan = new Map();
140
+ for (const mod of modules || []) {
141
+ plan.set(
142
+ mod.code,
143
+ decideChannelForModule({
144
+ code: mod.code,
145
+ channelOptions,
146
+ registryDefault: mod.defaultChannel,
147
+ }),
148
+ );
149
+ }
150
+ return plan;
151
+ }
152
+
153
+ /**
154
+ * Report any --pin CODE=TAG entries that don't correspond to a selected module.
155
+ * These get warned about but don't abort the install.
156
+ */
157
+ function orphanPinWarnings(channelOptions, selectedCodes) {
158
+ const warnings = [];
159
+ const selected = new Set(selectedCodes || []);
160
+ for (const code of channelOptions?.pins?.keys() || []) {
161
+ if (!selected.has(code)) {
162
+ warnings.push(`--pin for '${code}' has no effect (module not selected).`);
163
+ }
164
+ }
165
+ for (const code of channelOptions?.nextSet || []) {
166
+ if (!selected.has(code)) {
167
+ warnings.push(`--next for '${code}' has no effect (module not selected).`);
168
+ }
169
+ }
170
+ return warnings;
171
+ }
172
+
173
+ /**
174
+ * Warn when --pin / --next targets a bundled module (core, bmm). Those are
175
+ * shipped inside the installer binary — there's no git clone to override, so
176
+ * the flag has no effect. Users who actually want a prerelease core/bmm
177
+ * should use `npx bmad-method@next install`.
178
+ */
179
+ function bundledTargetWarnings(channelOptions, bundledCodes) {
180
+ const warnings = [];
181
+ const bundled = new Set(bundledCodes || []);
182
+ const hint = '(bundled module; use `npx bmad-method@next install` for a prerelease)';
183
+ for (const code of channelOptions?.pins?.keys() || []) {
184
+ if (bundled.has(code)) {
185
+ warnings.push(`--pin for '${code}' has no effect ${hint}.`);
186
+ }
187
+ }
188
+ for (const code of channelOptions?.nextSet || []) {
189
+ if (bundled.has(code)) {
190
+ warnings.push(`--next for '${code}' has no effect ${hint}.`);
191
+ }
192
+ }
193
+ return warnings;
194
+ }
195
+
196
+ module.exports = {
197
+ parseChannelOptions,
198
+ decideChannelForModule,
199
+ buildPlan,
200
+ orphanPinWarnings,
201
+ bundledTargetWarnings,
202
+ parsePinSpec,
203
+ };