bmad-method 4.6.0 → 4.6.2

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.
@@ -12,6 +12,11 @@ class WebBuilder {
12
12
  this.templatePath = path.join(this.rootDir, 'bmad-core', 'templates', 'web-agent-startup-instructions-template.md');
13
13
  }
14
14
 
15
+ parseYaml(content) {
16
+ const yaml = require('js-yaml');
17
+ return yaml.load(content);
18
+ }
19
+
15
20
  async cleanOutputDirs() {
16
21
  for (const dir of this.outputDirs) {
17
22
  try {
@@ -232,7 +237,7 @@ class WebBuilder {
232
237
  const agentContent = await fs.readFile(agentPath, 'utf8');
233
238
  sections.push(this.formatSection(`agents#${agentName}`, agentContent));
234
239
 
235
- // Resolve and add agent dependencies from expansion pack
240
+ // Resolve and add agent dependencies
236
241
  const agentYaml = agentContent.match(/```yaml\n([\s\S]*?)\n```/);
237
242
  if (agentYaml) {
238
243
  try {
@@ -240,16 +245,43 @@ class WebBuilder {
240
245
  const agentConfig = yaml.load(agentYaml[1]);
241
246
 
242
247
  if (agentConfig.dependencies) {
243
- // Add resources from expansion pack
248
+ // Add resources, first try expansion pack, then core
244
249
  for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
245
250
  if (Array.isArray(resources)) {
246
251
  for (const resourceName of resources) {
247
- const resourcePath = path.join(packDir, resourceType, `${resourceName}.md`);
248
- try {
249
- const resourceContent = await fs.readFile(resourcePath, 'utf8');
250
- sections.push(this.formatSection(`${resourceType}#${resourceName}`, resourceContent));
251
- } catch (error) {
252
- // Resource might not exist in expansion pack, that's ok
252
+ let found = false;
253
+ const extensions = ['.md', '.yml', '.yaml'];
254
+
255
+ // Try expansion pack first
256
+ for (const ext of extensions) {
257
+ const resourcePath = path.join(packDir, resourceType, `${resourceName}${ext}`);
258
+ try {
259
+ const resourceContent = await fs.readFile(resourcePath, 'utf8');
260
+ sections.push(this.formatSection(`${resourceType}#${resourceName}`, resourceContent));
261
+ found = true;
262
+ break;
263
+ } catch (error) {
264
+ // Not in expansion pack, continue
265
+ }
266
+ }
267
+
268
+ // If not found in expansion pack, try core
269
+ if (!found) {
270
+ for (const ext of extensions) {
271
+ const corePath = path.join(this.rootDir, 'bmad-core', resourceType, `${resourceName}${ext}`);
272
+ try {
273
+ const coreContent = await fs.readFile(corePath, 'utf8');
274
+ sections.push(this.formatSection(`${resourceType}#${resourceName}`, coreContent));
275
+ found = true;
276
+ break;
277
+ } catch (error) {
278
+ // Not in core either, continue
279
+ }
280
+ }
281
+ }
282
+
283
+ if (!found) {
284
+ console.warn(` ⚠ Dependency ${resourceType}#${resourceName} not found in expansion pack or core`);
253
285
  }
254
286
  }
255
287
  }
@@ -268,32 +300,164 @@ class WebBuilder {
268
300
 
269
301
  const sections = [template];
270
302
 
271
- // Add team configuration
303
+ // Add team configuration and parse to get agent list
272
304
  const teamContent = await fs.readFile(teamConfigPath, 'utf8');
273
305
  const teamFileName = path.basename(teamConfigPath, '.yml');
306
+ const teamConfig = this.parseYaml(teamContent);
274
307
  sections.push(this.formatSection(`agent-teams#${teamFileName}`, teamContent));
275
308
 
276
- // Add bmad-orchestrator (required for all teams)
277
- const orchestratorPath = path.join(this.rootDir, 'bmad-core', 'agents', 'bmad-orchestrator.md');
278
- const orchestratorContent = await fs.readFile(orchestratorPath, 'utf8');
279
- sections.push(this.formatSection('agents#bmad-orchestrator', orchestratorContent));
280
-
281
- // Add expansion pack agents
309
+ // Get list of expansion pack agents
310
+ const expansionAgents = new Set();
282
311
  const agentsDir = path.join(packDir, 'agents');
283
312
  try {
284
313
  const agentFiles = await fs.readdir(agentsDir);
285
314
  for (const agentFile of agentFiles.filter(f => f.endsWith('.md'))) {
286
- const agentPath = path.join(agentsDir, agentFile);
287
- const agentContent = await fs.readFile(agentPath, 'utf8');
288
315
  const agentName = agentFile.replace('.md', '');
289
- sections.push(this.formatSection(`agents#${agentName}`, agentContent));
316
+ expansionAgents.add(agentName);
290
317
  }
291
318
  } catch (error) {
292
319
  console.warn(` ⚠ No agents directory found in ${packName}`);
293
320
  }
294
321
 
295
- // Add expansion pack resources (templates, tasks, checklists)
322
+ // Build a map of all available expansion pack resources for override checking
323
+ const expansionResources = new Map();
296
324
  const resourceDirs = ['templates', 'tasks', 'checklists', 'workflows', 'data'];
325
+ for (const resourceDir of resourceDirs) {
326
+ const resourcePath = path.join(packDir, resourceDir);
327
+ try {
328
+ const resourceFiles = await fs.readdir(resourcePath);
329
+ for (const resourceFile of resourceFiles.filter(f => f.endsWith('.md') || f.endsWith('.yml'))) {
330
+ const fileName = resourceFile.replace(/\.(md|yml)$/, '');
331
+ expansionResources.set(`${resourceDir}#${fileName}`, true);
332
+ }
333
+ } catch (error) {
334
+ // Directory might not exist, that's fine
335
+ }
336
+ }
337
+
338
+ // Process all agents listed in team configuration
339
+ const agentsToProcess = teamConfig.agents || [];
340
+
341
+ // Ensure bmad-orchestrator is always included for teams
342
+ if (!agentsToProcess.includes('bmad-orchestrator')) {
343
+ console.warn(` ⚠ Team ${teamFileName} missing bmad-orchestrator, adding automatically`);
344
+ agentsToProcess.unshift('bmad-orchestrator');
345
+ }
346
+
347
+ // Track all dependencies from all agents (deduplicated)
348
+ const allDependencies = new Map();
349
+
350
+ for (const agentId of agentsToProcess) {
351
+
352
+ if (expansionAgents.has(agentId)) {
353
+ // Use expansion pack version (override)
354
+ const agentPath = path.join(agentsDir, `${agentId}.md`);
355
+ const agentContent = await fs.readFile(agentPath, 'utf8');
356
+ sections.push(this.formatSection(`agents#${agentId}`, agentContent));
357
+
358
+ // Parse and collect dependencies from expansion agent
359
+ const agentYaml = agentContent.match(/```yaml\n([\s\S]*?)\n```/);
360
+ if (agentYaml) {
361
+ try {
362
+ const agentConfig = this.parseYaml(agentYaml[1]);
363
+ if (agentConfig.dependencies) {
364
+ for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
365
+ if (Array.isArray(resources)) {
366
+ for (const resourceName of resources) {
367
+ const key = `${resourceType}#${resourceName}`;
368
+ if (!allDependencies.has(key)) {
369
+ allDependencies.set(key, { type: resourceType, name: resourceName });
370
+ }
371
+ }
372
+ }
373
+ }
374
+ }
375
+ } catch (error) {
376
+ console.debug(`Failed to parse agent YAML for ${agentId}:`, error.message);
377
+ }
378
+ }
379
+ } else {
380
+ // Use core BMAD version
381
+ try {
382
+ const coreAgentPath = path.join(this.rootDir, 'bmad-core', 'agents', `${agentId}.md`);
383
+ const coreAgentContent = await fs.readFile(coreAgentPath, 'utf8');
384
+ sections.push(this.formatSection(`agents#${agentId}`, coreAgentContent));
385
+
386
+ // Parse and collect dependencies from core agent
387
+ const agentYaml = coreAgentContent.match(/```yaml\n([\s\S]*?)\n```/);
388
+ if (agentYaml) {
389
+ try {
390
+ // Clean up the YAML to handle command descriptions after dashes
391
+ let yamlContent = agentYaml[1];
392
+ yamlContent = yamlContent.replace(/^(\s*-)(\s*"[^"]+")(\s*-\s*.*)$/gm, '$1$2');
393
+
394
+ const agentConfig = this.parseYaml(yamlContent);
395
+ if (agentConfig.dependencies) {
396
+ for (const [resourceType, resources] of Object.entries(agentConfig.dependencies)) {
397
+ if (Array.isArray(resources)) {
398
+ for (const resourceName of resources) {
399
+ const key = `${resourceType}#${resourceName}`;
400
+ if (!allDependencies.has(key)) {
401
+ allDependencies.set(key, { type: resourceType, name: resourceName });
402
+ }
403
+ }
404
+ }
405
+ }
406
+ }
407
+ } catch (error) {
408
+ console.debug(`Failed to parse agent YAML for ${agentId}:`, error.message);
409
+ }
410
+ }
411
+ } catch (error) {
412
+ console.warn(` ⚠ Agent ${agentId} not found in core or expansion pack`);
413
+ }
414
+ }
415
+ }
416
+
417
+ // Add all collected dependencies from agents
418
+ // Always prefer expansion pack versions if they exist
419
+ for (const [key, dep] of allDependencies) {
420
+ let found = false;
421
+ const extensions = ['.md', '.yml', '.yaml'];
422
+
423
+ // Always check expansion pack first, even if the dependency came from a core agent
424
+ if (expansionResources.has(key)) {
425
+ // We know it exists in expansion pack, find and load it
426
+ for (const ext of extensions) {
427
+ const expansionPath = path.join(packDir, dep.type, `${dep.name}${ext}`);
428
+ try {
429
+ const content = await fs.readFile(expansionPath, 'utf8');
430
+ sections.push(this.formatSection(key, content));
431
+ console.log(` ✓ Using expansion override for ${key}`);
432
+ found = true;
433
+ break;
434
+ } catch (error) {
435
+ // Try next extension
436
+ }
437
+ }
438
+ }
439
+
440
+ // If not found in expansion pack (or doesn't exist there), try core
441
+ if (!found) {
442
+ for (const ext of extensions) {
443
+ const corePath = path.join(this.rootDir, 'bmad-core', dep.type, `${dep.name}${ext}`);
444
+ try {
445
+ const content = await fs.readFile(corePath, 'utf8');
446
+ sections.push(this.formatSection(key, content));
447
+ found = true;
448
+ break;
449
+ } catch (error) {
450
+ // Not in core either, continue
451
+ }
452
+ }
453
+ }
454
+
455
+ if (!found) {
456
+ console.warn(` ⚠ Dependency ${key} not found in expansion pack or core`);
457
+ }
458
+ }
459
+
460
+ // Add remaining expansion pack resources not already included as dependencies
297
461
  for (const resourceDir of resourceDirs) {
298
462
  const resourcePath = path.join(packDir, resourceDir);
299
463
  try {
@@ -302,7 +466,12 @@ class WebBuilder {
302
466
  const filePath = path.join(resourcePath, resourceFile);
303
467
  const fileContent = await fs.readFile(filePath, 'utf8');
304
468
  const fileName = resourceFile.replace(/\.(md|yml)$/, '');
305
- sections.push(this.formatSection(`${resourceDir}#${fileName}`, fileContent));
469
+
470
+ // Only add if not already included as a dependency
471
+ const resourceKey = `${resourceDir}#${fileName}`;
472
+ if (!allDependencies.has(resourceKey)) {
473
+ sections.push(this.formatSection(resourceKey, fileContent));
474
+ }
306
475
  }
307
476
  } catch (error) {
308
477
  // Directory might not exist, that's fine
@@ -425,7 +425,10 @@ class Installer {
425
425
  console.log(chalk.cyan("\n📦 Starting v3 to v4 upgrade process..."));
426
426
  const V3ToV4Upgrader = require("../../upgraders/v3-to-v4-upgrader");
427
427
  const upgrader = new V3ToV4Upgrader();
428
- return await upgrader.upgrade({ projectPath: installDir });
428
+ return await upgrader.upgrade({
429
+ projectPath: installDir,
430
+ ides: config.ides || [] // Pass IDE selections from initial config
431
+ });
429
432
  }
430
433
  case "alongside":
431
434
  return await this.performFreshInstall(config, installDir, spinner);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmad-method",
3
- "version": "4.6.0",
3
+ "version": "4.6.2",
4
4
  "description": "BMAD Method installer - AI-powered Agile development framework",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -98,7 +98,7 @@ class V3ToV4Upgrader {
98
98
 
99
99
  // 8. Setup IDE
100
100
  if (!options.dryRun) {
101
- await this.setupIDE(projectPath);
101
+ await this.setupIDE(projectPath, options.ides);
102
102
  }
103
103
 
104
104
  // 9. Show completion report
@@ -379,8 +379,8 @@ class V3ToV4Upgrader {
379
379
  const spinner = ora("Installing V4 structure...").start();
380
380
 
381
381
  try {
382
- // Get the source .bmad-core directory
383
- const sourcePath = path.join(__dirname, "..", "..", ".bmad-core");
382
+ // Get the source bmad-core directory (without dot prefix)
383
+ const sourcePath = path.join(__dirname, "..", "..", "bmad-core");
384
384
  const destPath = path.join(projectPath, ".bmad-core");
385
385
 
386
386
  // Copy .bmad-core
@@ -545,47 +545,37 @@ class V3ToV4Upgrader {
545
545
  }
546
546
  }
547
547
 
548
- async setupIDE(projectPath) {
549
- const { ide } = await inquirer.prompt([
550
- {
551
- type: "list",
552
- name: "ide",
553
- message: "Which IDE are you using?",
554
- choices: [
555
- { name: "Cursor", value: "cursor" },
556
- { name: "Claude Code", value: "claude-code" },
557
- { name: "Windsurf", value: "windsurf" },
558
- { name: "Roo Code", value: "roo" },
559
- { name: "VS Code", value: "skip" },
560
- { name: "Other/Skip", value: "skip" },
561
- ],
562
- },
563
- ]);
564
-
565
- const selectedIde = ide === "skip" ? null : ide;
566
-
567
- if (selectedIde) {
568
- const ideSetup = require("../installer/lib/ide-setup");
569
- const spinner = ora("Setting up IDE rules for all agents...").start();
570
-
571
- try {
572
- await ideSetup.setup(selectedIde, projectPath);
573
- spinner.succeed("IDE setup complete!");
548
+ async setupIDE(projectPath, selectedIdes) {
549
+ // Use the IDE selections passed from the installer
550
+ if (!selectedIdes || selectedIdes.length === 0) {
551
+ console.log(chalk.dim("No IDE setup requested - skipping"));
552
+ return;
553
+ }
574
554
 
575
- const ideMessages = {
576
- cursor: "Rules created in .cursor/rules/",
577
- "claude-code": "Commands created in .claude/commands/",
578
- windsurf: "Rules created in .windsurf/rules/",
579
- roo: "Custom modes created in .roomodes",
580
- };
555
+ const ideSetup = require("../installer/lib/ide-setup");
556
+ const spinner = ora("Setting up IDE rules for all agents...").start();
581
557
 
582
- console.log(chalk.green(`- ${ideMessages[selectedIde]}`));
583
- } catch (error) {
584
- spinner.fail("IDE setup failed");
585
- console.error(
586
- chalk.yellow("IDE setup failed, but upgrade is complete.")
587
- );
558
+ try {
559
+ const ideMessages = {
560
+ cursor: "Rules created in .cursor/rules/",
561
+ "claude-code": "Commands created in .claude/commands/",
562
+ windsurf: "Rules created in .windsurf/rules/",
563
+ roo: "Custom modes created in .roomodes",
564
+ };
565
+
566
+ // Setup each selected IDE
567
+ for (const ide of selectedIdes) {
568
+ spinner.text = `Setting up ${ide}...`;
569
+ await ideSetup.setup(ide, projectPath);
570
+ console.log(chalk.green(`\n✓ ${ideMessages[ide]}`));
588
571
  }
572
+
573
+ spinner.succeed(`IDE setup complete for ${selectedIdes.length} IDE(s)!`);
574
+ } catch (error) {
575
+ spinner.fail("IDE setup failed");
576
+ console.error(
577
+ chalk.yellow("IDE setup failed, but upgrade is complete.")
578
+ );
589
579
  }
590
580
  }
591
581