bmad-method 6.0.0-alpha.14 → 6.0.0-alpha.15

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 (79) hide show
  1. package/.coderabbit.yaml +36 -0
  2. package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +4 -4
  3. package/CHANGELOG.md +136 -408
  4. package/README.md +4 -1
  5. package/docs/custom-content-installation.md +245 -0
  6. package/docs/index.md +2 -2
  7. package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
  8. package/docs/web-bundles-gemini-gpt-guide.md +1 -1
  9. package/example-custom-content/README.md +4 -0
  10. package/example-custom-content/agents/commit-poet/commit-poet.agent.yaml +1 -1
  11. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/instructions.md +1 -1
  12. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
  13. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
  14. package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
  15. package/example-custom-content/agents/toolsmith/toolsmith.agent.yaml +1 -1
  16. package/example-custom-content/{custom.yaml → module.yaml} +1 -0
  17. package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +2 -2
  18. package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +1 -1
  19. package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +1 -1
  20. package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +1 -1
  21. package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +1 -1
  22. package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +1 -1
  23. package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +1 -1
  24. package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +1 -1
  25. package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +1 -1
  26. package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +1 -1
  27. package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +1 -1
  28. package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +1 -1
  29. package/example-custom-content/workflows/quiz-master/workflow.md +1 -1
  30. package/example-custom-module/mwm/README.md +5 -0
  31. package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +1 -0
  32. package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +3 -2
  33. package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +3 -2
  34. package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +1 -0
  35. package/example-custom-module/mwm/{_module-installer/install-config.yaml → module.yaml} +1 -0
  36. package/package.json +1 -1
  37. package/src/core/_module-installer/installer.js +1 -1
  38. package/src/modules/bmb/_module-installer/installer.js +1 -1
  39. package/src/modules/bmb/docs/agents/index.md +1 -1
  40. package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +3 -3
  41. package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +1 -1
  42. package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +8 -8
  43. package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +2 -1
  44. package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +3 -2
  45. package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +3 -3
  46. package/src/modules/bmb/workflows/create-module/templates/installer.template.js +1 -1
  47. package/src/modules/bmb/workflows/create-module/validation.md +3 -3
  48. package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +1 -1
  49. package/src/modules/bmb/workflows/create-workflow/steps/step-07-build.md +1 -1
  50. package/src/modules/bmgd/README.md +2 -1
  51. package/src/modules/bmm/_module-installer/installer.js +1 -1
  52. package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
  53. package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
  54. package/src/modules/cis/_module-installer/installer.js +1 -1
  55. package/tools/cli/README.md +4 -4
  56. package/tools/cli/installers/lib/core/config-collector.js +16 -8
  57. package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
  58. package/tools/cli/installers/lib/core/detector.js +8 -4
  59. package/tools/cli/installers/lib/core/installer.js +815 -23
  60. package/tools/cli/installers/lib/core/manifest-generator.js +176 -13
  61. package/tools/cli/installers/lib/core/manifest.js +47 -0
  62. package/tools/cli/installers/lib/custom/handler.js +150 -20
  63. package/tools/cli/installers/lib/modules/manager.js +78 -32
  64. package/tools/cli/lib/agent/compiler.js +3 -11
  65. package/tools/cli/lib/agent/installer.js +2 -1
  66. package/tools/cli/lib/cli-utils.js +21 -4
  67. package/tools/cli/lib/ui.js +499 -11
  68. package/tools/maintainer/review-pr-README.md +55 -0
  69. package/tools/maintainer/review-pr.md +242 -0
  70. package/tools/migrate-custom-module-paths.js +124 -0
  71. package/bmad-method-6.0.0-alpha.14.tgz +0 -0
  72. package/docs/custom-agent-installation.md +0 -137
  73. package/example-custom-content/workflows/quiz-master/workflow-plan-quiz-master.md +0 -269
  74. /package/src/core/{_module-installer/install-config.yaml → module.yaml} +0 -0
  75. /package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +0 -0
  76. /package/src/modules/bmb/workflows/create-module/templates/{install-config.template.yaml → module.template.yaml} +0 -0
  77. /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
  78. /package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +0 -0
  79. /package/src/modules/cis/{_module-installer/install-config.yaml → module.yaml} +0 -0
@@ -22,11 +22,12 @@ const { getProjectRoot, getSourcePath, getModulePath } = require('../../../lib/p
22
22
  * await manager.install('core-module', '/path/to/bmad');
23
23
  */
24
24
  class ModuleManager {
25
- constructor() {
25
+ constructor(options = {}) {
26
26
  // Path to source modules directory
27
27
  this.modulesSourcePath = getSourcePath('modules');
28
28
  this.xmlHandler = new XmlHandler();
29
29
  this.bmadFolderName = 'bmad'; // Default, can be overridden
30
+ this.scanProjectForModules = options.scanProjectForModules !== false; // Default to true for backward compatibility
30
31
  }
31
32
 
32
33
  /**
@@ -106,7 +107,7 @@ class ModuleManager {
106
107
  }
107
108
 
108
109
  /**
109
- * Find all modules in the project by searching for install-config.yaml files
110
+ * Find all modules in the project by searching for module.yaml files
110
111
  * @returns {Array} List of module paths
111
112
  */
112
113
  async findModulesInProject() {
@@ -143,12 +144,14 @@ class ModuleManager {
143
144
  continue;
144
145
  }
145
146
 
146
- // Check if this directory contains a module (install-config.yaml OR custom.yaml)
147
- const installerConfigPath = path.join(fullPath, '_module-installer', 'install-config.yaml');
147
+ // Check if this directory contains a module (module.yaml OR custom.yaml)
148
+ const moduleConfigPath = path.join(fullPath, 'module.yaml');
149
+ const installerConfigPath = path.join(fullPath, '_module-installer', 'module.yaml');
148
150
  const customConfigPath = path.join(fullPath, '_module-installer', 'custom.yaml');
149
151
  const rootCustomConfigPath = path.join(fullPath, 'custom.yaml');
150
152
 
151
153
  if (
154
+ (await fs.pathExists(moduleConfigPath)) ||
152
155
  (await fs.pathExists(installerConfigPath)) ||
153
156
  (await fs.pathExists(customConfigPath)) ||
154
157
  (await fs.pathExists(rootCustomConfigPath))
@@ -175,10 +178,11 @@ class ModuleManager {
175
178
 
176
179
  /**
177
180
  * List all available modules (excluding core which is always installed)
178
- * @returns {Array} List of available modules with metadata
181
+ * @returns {Object} Object with modules array and customModules array
179
182
  */
180
183
  async listAvailable() {
181
184
  const modules = [];
185
+ const customModules = [];
182
186
 
183
187
  // First, scan src/modules (the standard location)
184
188
  if (await fs.pathExists(this.modulesSourcePath)) {
@@ -187,12 +191,17 @@ class ModuleManager {
187
191
  for (const entry of entries) {
188
192
  if (entry.isDirectory()) {
189
193
  const modulePath = path.join(this.modulesSourcePath, entry.name);
190
- // Check for module structure (install-config.yaml OR custom.yaml)
191
- const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
194
+ // Check for module structure (module.yaml OR custom.yaml)
195
+ const moduleConfigPath = path.join(modulePath, 'module.yaml');
196
+ const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
192
197
  const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
193
198
 
194
199
  // Skip if this doesn't look like a module
195
- if (!(await fs.pathExists(installerConfigPath)) && !(await fs.pathExists(customConfigPath))) {
200
+ if (
201
+ !(await fs.pathExists(moduleConfigPath)) &&
202
+ !(await fs.pathExists(installerConfigPath)) &&
203
+ !(await fs.pathExists(customConfigPath))
204
+ ) {
196
205
  continue;
197
206
  }
198
207
 
@@ -209,25 +218,50 @@ class ModuleManager {
209
218
  }
210
219
  }
211
220
 
212
- // Then, find all other modules in the project
213
- const otherModulePaths = await this.findModulesInProject();
214
- for (const modulePath of otherModulePaths) {
215
- const moduleName = path.basename(modulePath);
216
- const relativePath = path.relative(getProjectRoot(), modulePath);
221
+ // Then, find all other modules in the project (only if scanning is enabled)
222
+ if (this.scanProjectForModules) {
223
+ const otherModulePaths = await this.findModulesInProject();
224
+ for (const modulePath of otherModulePaths) {
225
+ const moduleName = path.basename(modulePath);
226
+ const relativePath = path.relative(getProjectRoot(), modulePath);
217
227
 
218
- // Skip core module - it's always installed first and not selectable
219
- if (moduleName === 'core') {
220
- continue;
228
+ // Skip core module - it's always installed first and not selectable
229
+ if (moduleName === 'core') {
230
+ continue;
231
+ }
232
+
233
+ const moduleInfo = await this.getModuleInfo(modulePath, moduleName, relativePath);
234
+ if (moduleInfo && !modules.some((m) => m.id === moduleInfo.id) && !customModules.some((m) => m.id === moduleInfo.id)) {
235
+ // Avoid duplicates - skip if we already have this module ID
236
+ if (moduleInfo.isCustom) {
237
+ customModules.push(moduleInfo);
238
+ } else {
239
+ modules.push(moduleInfo);
240
+ }
241
+ }
221
242
  }
222
243
 
223
- const moduleInfo = await this.getModuleInfo(modulePath, moduleName, relativePath);
224
- if (moduleInfo && !modules.some((m) => m.id === moduleInfo.id)) {
225
- // Avoid duplicates - skip if we already have this module ID
226
- modules.push(moduleInfo);
244
+ // Also check for cached custom modules in _cfg/custom/
245
+ if (this.bmadDir) {
246
+ const customCacheDir = path.join(this.bmadDir, '_cfg', 'custom');
247
+ if (await fs.pathExists(customCacheDir)) {
248
+ const cacheEntries = await fs.readdir(customCacheDir, { withFileTypes: true });
249
+ for (const entry of cacheEntries) {
250
+ if (entry.isDirectory()) {
251
+ const cachePath = path.join(customCacheDir, entry.name);
252
+ const moduleInfo = await this.getModuleInfo(cachePath, entry.name, '_cfg/custom');
253
+ if (moduleInfo && !modules.some((m) => m.id === moduleInfo.id) && !customModules.some((m) => m.id === moduleInfo.id)) {
254
+ moduleInfo.isCustom = true;
255
+ moduleInfo.fromCache = true;
256
+ customModules.push(moduleInfo);
257
+ }
258
+ }
259
+ }
260
+ }
227
261
  }
228
262
  }
229
263
 
230
- return modules;
264
+ return { modules, customModules };
231
265
  }
232
266
 
233
267
  /**
@@ -238,13 +272,16 @@ class ModuleManager {
238
272
  * @returns {Object|null} Module info or null if not a valid module
239
273
  */
240
274
  async getModuleInfo(modulePath, defaultName, sourceDescription) {
241
- // Check for module structure (install-config.yaml OR custom.yaml)
242
- const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
275
+ // Check for module structure (module.yaml OR custom.yaml)
276
+ const moduleConfigPath = path.join(modulePath, 'module.yaml');
277
+ const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
243
278
  const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
244
279
  const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
245
280
  let configPath = null;
246
281
 
247
- if (await fs.pathExists(installerConfigPath)) {
282
+ if (await fs.pathExists(moduleConfigPath)) {
283
+ configPath = moduleConfigPath;
284
+ } else if (await fs.pathExists(installerConfigPath)) {
248
285
  configPath = installerConfigPath;
249
286
  } else if (await fs.pathExists(customConfigPath)) {
250
287
  configPath = customConfigPath;
@@ -305,10 +342,11 @@ class ModuleManager {
305
342
  // First, check src/modules
306
343
  const srcModulePath = path.join(this.modulesSourcePath, moduleName);
307
344
  if (await fs.pathExists(srcModulePath)) {
308
- // Check if this looks like a module (has install-config.yaml)
309
- const installerConfigPath = path.join(srcModulePath, '_module-installer', 'install-config.yaml');
345
+ // Check if this looks like a module (has module.yaml)
346
+ const moduleConfigPath = path.join(srcModulePath, 'module.yaml');
347
+ const installerConfigPath = path.join(srcModulePath, '_module-installer', 'module.yaml');
310
348
 
311
- if (await fs.pathExists(installerConfigPath)) {
349
+ if ((await fs.pathExists(moduleConfigPath)) || (await fs.pathExists(installerConfigPath))) {
312
350
  return srcModulePath;
313
351
  }
314
352
 
@@ -330,12 +368,15 @@ class ModuleManager {
330
368
  // Also check by module ID (not just folder name)
331
369
  // Need to read configs to match by ID
332
370
  for (const modulePath of allModulePaths) {
333
- const installerConfigPath = path.join(modulePath, '_module-installer', 'install-config.yaml');
371
+ const moduleConfigPath = path.join(modulePath, 'module.yaml');
372
+ const installerConfigPath = path.join(modulePath, '_module-installer', 'module.yaml');
334
373
  const customConfigPath = path.join(modulePath, '_module-installer', 'custom.yaml');
335
374
  const rootCustomConfigPath = path.join(modulePath, 'custom.yaml');
336
375
 
337
376
  let configPath = null;
338
- if (await fs.pathExists(installerConfigPath)) {
377
+ if (await fs.pathExists(moduleConfigPath)) {
378
+ configPath = moduleConfigPath;
379
+ } else if (await fs.pathExists(installerConfigPath)) {
339
380
  configPath = installerConfigPath;
340
381
  } else if (await fs.pathExists(customConfigPath)) {
341
382
  configPath = customConfigPath;
@@ -576,7 +617,7 @@ class ModuleManager {
576
617
  }
577
618
 
578
619
  // Skip _module-installer directory - it's only needed at install time
579
- if (file.startsWith('_module-installer/')) {
620
+ if (file.startsWith('_module-installer/') || file === 'module.yaml') {
580
621
  continue;
581
622
  }
582
623
 
@@ -812,8 +853,13 @@ class ModuleManager {
812
853
  // Compile with customizations if any
813
854
  const { xml } = compileAgent(yamlContent, {}, agentName, relativePath, { config: this.coreConfig });
814
855
 
815
- // Write the compiled MD file
816
- await fs.writeFile(targetMdPath, xml, 'utf8');
856
+ // Replace {bmad_folder} placeholder if needed
857
+ if (xml.includes('{bmad_folder}') && this.bmadFolderName) {
858
+ const processedXml = xml.replaceAll('{bmad_folder}', this.bmadFolderName);
859
+ await fs.writeFile(targetMdPath, processedXml, 'utf8');
860
+ } else {
861
+ await fs.writeFile(targetMdPath, xml, 'utf8');
862
+ }
817
863
 
818
864
  // Copy sidecar files if agent has hasSidecar flag
819
865
  if (hasSidecar) {
@@ -445,17 +445,9 @@ function compileAgent(yamlContent, answers = {}, agentName = '', targetPath = ''
445
445
  // Parse YAML
446
446
  const agentYaml = yaml.parse(yamlContent);
447
447
 
448
- // Inject custom agent name into metadata.name if provided
449
- // This is the user's chosen persona name (e.g., "Fred" instead of "Inkwell Von Comitizen")
450
- if (agentName && agentYaml.agent && agentYaml.agent.metadata) {
451
- // Convert kebab-case to title case for the name field
452
- // e.g., "fred-commit-poet" → "Fred Commit Poet"
453
- const titleCaseName = agentName
454
- .split('-')
455
- .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
456
- .join(' ');
457
- agentYaml.agent.metadata.name = titleCaseName;
458
- }
448
+ // Note: agentName parameter is for UI display only, not for modifying the YAML
449
+ // The persona name (metadata.name) should always come from the YAML file
450
+ // We should NEVER modify metadata.name as it's part of the agent's identity
459
451
 
460
452
  // Extract install_config
461
453
  const installConfig = extractInstallConfig(agentYaml);
@@ -242,7 +242,8 @@ function installAgent(agentInfo, answers, targetPath, options = {}) {
242
242
  const { xml, metadata, processedYaml } = compileAgent(fs.readFileSync(agentInfo.yamlFile, 'utf8'), answers);
243
243
 
244
244
  // Determine target agent folder name
245
- const agentFolderName = metadata.name ? metadata.name.toLowerCase().replaceAll(/\s+/g, '-') : agentInfo.name;
245
+ // Use the folder name from agentInfo, NOT the persona name from metadata
246
+ const agentFolderName = agentInfo.name;
246
247
 
247
248
  const agentTargetDir = path.join(targetPath, agentFolderName);
248
249
 
@@ -3,6 +3,7 @@ const boxen = require('boxen');
3
3
  const wrapAnsi = require('wrap-ansi');
4
4
  const figlet = require('figlet');
5
5
  const path = require('node:path');
6
+ const os = require('node:os');
6
7
 
7
8
  const CLIUtils = {
8
9
  /**
@@ -84,8 +85,8 @@ const CLIUtils = {
84
85
  /**
85
86
  * Display module configuration header
86
87
  * @param {string} moduleName - Module name (fallback if no custom header)
87
- * @param {string} header - Custom header from install-config.yaml
88
- * @param {string} subheader - Custom subheader from install-config.yaml
88
+ * @param {string} header - Custom header from module.yaml
89
+ * @param {string} subheader - Custom subheader from module.yaml
89
90
  */
90
91
  displayModuleConfigHeader(moduleName, header = null, subheader = null) {
91
92
  // Simple blue banner with custom header/subheader if provided
@@ -100,8 +101,8 @@ const CLIUtils = {
100
101
  /**
101
102
  * Display module with no custom configuration
102
103
  * @param {string} moduleName - Module name (fallback if no custom header)
103
- * @param {string} header - Custom header from install-config.yaml
104
- * @param {string} subheader - Custom subheader from install-config.yaml
104
+ * @param {string} header - Custom header from module.yaml
105
+ * @param {string} subheader - Custom subheader from module.yaml
105
106
  */
106
107
  displayModuleNoConfig(moduleName, header = null, subheader = null) {
107
108
  // Show full banner with header/subheader, just like modules with config
@@ -205,6 +206,22 @@ const CLIUtils = {
205
206
  // No longer clear screen or show boxes - just a simple completion message
206
207
  // This is deprecated but kept for backwards compatibility
207
208
  },
209
+
210
+ /**
211
+ * Expand path with ~ expansion
212
+ * @param {string} inputPath - Path to expand
213
+ * @returns {string} Expanded path
214
+ */
215
+ expandPath(inputPath) {
216
+ if (!inputPath) return inputPath;
217
+
218
+ // Expand ~ to home directory
219
+ if (inputPath.startsWith('~')) {
220
+ return path.join(os.homedir(), inputPath.slice(1));
221
+ }
222
+
223
+ return inputPath;
224
+ },
208
225
  };
209
226
 
210
227
  module.exports = { CLIUtils };