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.
- package/.coderabbit.yaml +36 -0
- package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +4 -4
- package/CHANGELOG.md +136 -408
- package/README.md +4 -1
- package/docs/custom-content-installation.md +245 -0
- package/docs/index.md +2 -2
- package/docs/installers-bundlers/installers-modules-platforms-reference.md +6 -5
- package/docs/web-bundles-gemini-gpt-guide.md +1 -1
- package/example-custom-content/README.md +4 -0
- package/example-custom-content/agents/commit-poet/commit-poet.agent.yaml +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/instructions.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/docs.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/installers.md +1 -1
- package/example-custom-content/agents/toolsmith/toolsmith-sidecar/knowledge/modules.md +2 -2
- package/example-custom-content/agents/toolsmith/toolsmith.agent.yaml +1 -1
- package/example-custom-content/{custom.yaml → module.yaml} +1 -0
- package/example-custom-content/workflows/quiz-master/steps/step-01-init.md +2 -2
- package/example-custom-content/workflows/quiz-master/steps/step-02-q1.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-03-q2.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-04-q3.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-05-q4.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-06-q5.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-07-q6.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-08-q7.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-09-q8.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-10-q9.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-11-q10.md +1 -1
- package/example-custom-content/workflows/quiz-master/steps/step-12-results.md +1 -1
- package/example-custom-content/workflows/quiz-master/workflow.md +1 -1
- package/example-custom-module/mwm/README.md +5 -0
- package/example-custom-module/mwm/agents/cbt-coach/cbt-coach.agent.yaml +1 -0
- package/example-custom-module/mwm/agents/crisis-navigator.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/meditation-guide.agent.yaml +3 -2
- package/example-custom-module/mwm/agents/wellness-companion/wellness-companion.agent.yaml +1 -0
- package/example-custom-module/mwm/{_module-installer/install-config.yaml → module.yaml} +1 -0
- package/package.json +1 -1
- package/src/core/_module-installer/installer.js +1 -1
- package/src/modules/bmb/_module-installer/installer.js +1 -1
- package/src/modules/bmb/docs/agents/index.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-04-structure.md +3 -3
- package/src/modules/bmb/workflows/create-module/steps/step-05-config.md +1 -1
- package/src/modules/bmb/workflows/create-module/steps/step-08-installer.md +8 -8
- package/src/modules/bmb/workflows/create-module/steps/step-09-documentation.md +2 -1
- package/src/modules/bmb/workflows/create-module/steps/step-10-roadmap.md +3 -2
- package/src/modules/bmb/workflows/create-module/steps/step-11-validate.md +3 -3
- package/src/modules/bmb/workflows/create-module/templates/installer.template.js +1 -1
- package/src/modules/bmb/workflows/create-module/validation.md +3 -3
- package/src/modules/bmb/workflows/create-workflow/steps/step-01-init.md +1 -1
- package/src/modules/bmb/workflows/create-workflow/steps/step-07-build.md +1 -1
- package/src/modules/bmgd/README.md +2 -1
- package/src/modules/bmm/_module-installer/installer.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/claude-code.js +1 -1
- package/src/modules/bmm/_module-installer/platform-specifics/windsurf.js +1 -1
- package/src/modules/cis/_module-installer/installer.js +1 -1
- package/tools/cli/README.md +4 -4
- package/tools/cli/installers/lib/core/config-collector.js +16 -8
- package/tools/cli/installers/lib/core/custom-module-cache.js +239 -0
- package/tools/cli/installers/lib/core/detector.js +8 -4
- package/tools/cli/installers/lib/core/installer.js +815 -23
- package/tools/cli/installers/lib/core/manifest-generator.js +176 -13
- package/tools/cli/installers/lib/core/manifest.js +47 -0
- package/tools/cli/installers/lib/custom/handler.js +150 -20
- package/tools/cli/installers/lib/modules/manager.js +78 -32
- package/tools/cli/lib/agent/compiler.js +3 -11
- package/tools/cli/lib/agent/installer.js +2 -1
- package/tools/cli/lib/cli-utils.js +21 -4
- package/tools/cli/lib/ui.js +499 -11
- package/tools/maintainer/review-pr-README.md +55 -0
- package/tools/maintainer/review-pr.md +242 -0
- package/tools/migrate-custom-module-paths.js +124 -0
- package/bmad-method-6.0.0-alpha.14.tgz +0 -0
- package/docs/custom-agent-installation.md +0 -137
- package/example-custom-content/workflows/quiz-master/workflow-plan-quiz-master.md +0 -269
- /package/src/core/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmb/workflows/create-module/templates/{install-config.template.yaml → module.template.yaml} +0 -0
- /package/src/modules/bmgd/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /package/src/modules/bmm/{_module-installer/install-config.yaml → module.yaml} +0 -0
- /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
|
|
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 (
|
|
147
|
-
const
|
|
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 {
|
|
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 (
|
|
191
|
-
const
|
|
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 (
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
const
|
|
216
|
-
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
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 (
|
|
242
|
-
const
|
|
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(
|
|
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
|
|
309
|
-
const
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
816
|
-
|
|
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
|
-
//
|
|
449
|
-
//
|
|
450
|
-
|
|
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
|
-
|
|
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
|
|
88
|
-
* @param {string} subheader - Custom subheader from
|
|
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
|
|
104
|
-
* @param {string} subheader - Custom subheader from
|
|
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 };
|