bmad-method 4.19.2 → 4.21.0

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 (131) hide show
  1. package/.github/FUNDING.yml +15 -0
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  3. package/.github/ISSUE_TEMPLATE/feature_request.md +22 -0
  4. package/.vscode/settings.json +6 -76
  5. package/CHANGELOG.md +19 -0
  6. package/CONTRIBUTING.md +39 -5
  7. package/GUIDING-PRINCIPLES.md +2 -2
  8. package/LICENSE +1 -1
  9. package/README.md +178 -220
  10. package/bmad-core/agents/dev.md +1 -1
  11. package/bmad-core/core-config.yml +1 -0
  12. package/bmad-core/data/bmad-kb.md +1 -1
  13. package/dist/agents/analyst.txt +1 -1
  14. package/dist/agents/bmad-master.txt +1 -1
  15. package/dist/agents/bmad-orchestrator.txt +1 -1
  16. package/dist/agents/dev.txt +1 -1
  17. package/dist/teams/team-all.txt +40 -40
  18. package/dist/teams/team-fullstack.txt +39 -39
  19. package/dist/teams/team-ide-minimal.txt +2 -2
  20. package/dist/teams/team-no-ui.txt +13 -13
  21. package/docs/agentic-tools/claude-code-guide.md +36 -0
  22. package/docs/agentic-tools/cline-guide.md +42 -0
  23. package/docs/agentic-tools/cursor-guide.md +37 -0
  24. package/docs/agentic-tools/gemini-cli-guide.md +46 -0
  25. package/docs/agentic-tools/roo-code-guide.md +46 -0
  26. package/docs/agentic-tools/windsurf-guide.md +37 -0
  27. package/docs/core-architecture.md +174 -6
  28. package/docs/expansion-packs.md +15 -0
  29. package/docs/how-to-contribute-with-pull-requests.md +24 -7
  30. package/docs/user-guide.md +283 -36
  31. package/docs/versioning-and-releases.md +2 -10
  32. package/docs/versions.md +0 -1
  33. package/docs/working-in-the-brownfield.md +4 -8
  34. package/expansion-packs/bmad-2d-phaser-game-dev/config.yml +4 -2
  35. package/expansion-packs/bmad-creator-tools/config.yml +1 -1
  36. package/expansion-packs/bmad-infrastructure-devops/config.yml +5 -2
  37. package/package.json +15 -1
  38. package/tools/bump-all-versions.js +107 -0
  39. package/tools/bump-core-version.js +57 -0
  40. package/tools/bump-expansion-version.js +78 -0
  41. package/tools/installer/bin/bmad.js +81 -160
  42. package/tools/installer/lib/file-manager.js +90 -2
  43. package/tools/installer/lib/installer.js +515 -64
  44. package/tools/installer/package.json +1 -1
  45. package/tools/update-expansion-version.js +54 -0
  46. package/.claude/commands/analyst.md +0 -68
  47. package/.claude/commands/architect.md +0 -68
  48. package/.claude/commands/bmad-master.md +0 -105
  49. package/.claude/commands/bmad-orchestrator.md +0 -130
  50. package/.claude/commands/bmad-the-creator.md +0 -57
  51. package/.claude/commands/dev.md +0 -69
  52. package/.claude/commands/game-designer.md +0 -62
  53. package/.claude/commands/game-developer.md +0 -70
  54. package/.claude/commands/game-sm.md +0 -55
  55. package/.claude/commands/infra-devops-platform.md +0 -63
  56. package/.claude/commands/pm.md +0 -65
  57. package/.claude/commands/po.md +0 -67
  58. package/.claude/commands/qa.md +0 -54
  59. package/.claude/commands/sm.md +0 -55
  60. package/.claude/commands/ux-expert.md +0 -67
  61. package/.clinerules/01-bmad-master.md +0 -116
  62. package/.clinerules/02-bmad-orchestrator.md +0 -141
  63. package/.clinerules/03-pm.md +0 -76
  64. package/.clinerules/04-analyst.md +0 -79
  65. package/.clinerules/05-architect.md +0 -79
  66. package/.clinerules/06-po.md +0 -78
  67. package/.clinerules/07-sm.md +0 -66
  68. package/.clinerules/08-dev.md +0 -80
  69. package/.clinerules/09-qa.md +0 -65
  70. package/.clinerules/10-ux-expert.md +0 -78
  71. package/.clinerules/11-bmad-the-creator.md +0 -68
  72. package/.clinerules/12-game-designer.md +0 -73
  73. package/.clinerules/13-game-developer.md +0 -81
  74. package/.clinerules/14-game-sm.md +0 -66
  75. package/.clinerules/15-infra-devops-platform.md +0 -74
  76. package/.cursor/rules/analyst.mdc +0 -82
  77. package/.cursor/rules/architect.mdc +0 -82
  78. package/.cursor/rules/bmad-master.mdc +0 -119
  79. package/.cursor/rules/bmad-orchestrator.mdc +0 -144
  80. package/.cursor/rules/bmad-the-creator.mdc +0 -71
  81. package/.cursor/rules/dev.mdc +0 -83
  82. package/.cursor/rules/game-designer.mdc +0 -76
  83. package/.cursor/rules/game-developer.mdc +0 -84
  84. package/.cursor/rules/game-sm.mdc +0 -69
  85. package/.cursor/rules/infra-devops-platform.mdc +0 -77
  86. package/.cursor/rules/pm.mdc +0 -79
  87. package/.cursor/rules/po.mdc +0 -81
  88. package/.cursor/rules/qa.mdc +0 -68
  89. package/.cursor/rules/sm.mdc +0 -69
  90. package/.cursor/rules/ux-expert.mdc +0 -81
  91. package/.gemini/agents/analyst.md +0 -64
  92. package/.gemini/agents/architect.md +0 -64
  93. package/.gemini/agents/bmad-master.md +0 -101
  94. package/.gemini/agents/bmad-orchestrator.md +0 -126
  95. package/.gemini/agents/bmad-the-creator.md +0 -53
  96. package/.gemini/agents/dev.md +0 -65
  97. package/.gemini/agents/game-designer.md +0 -58
  98. package/.gemini/agents/game-developer.md +0 -66
  99. package/.gemini/agents/game-sm.md +0 -51
  100. package/.gemini/agents/infra-devops-platform.md +0 -59
  101. package/.gemini/agents/pm.md +0 -61
  102. package/.gemini/agents/po.md +0 -63
  103. package/.gemini/agents/qa.md +0 -50
  104. package/.gemini/agents/sm.md +0 -51
  105. package/.gemini/agents/ux-expert.md +0 -63
  106. package/.gemini/settings.json +0 -20
  107. package/.roomodes +0 -139
  108. package/.vscode/extensions.json +0 -6
  109. package/.vscode/tasks.json +0 -41
  110. package/.windsurf/rules/analyst.md +0 -76
  111. package/.windsurf/rules/architect.md +0 -76
  112. package/.windsurf/rules/bmad-master.md +0 -113
  113. package/.windsurf/rules/bmad-orchestrator.md +0 -138
  114. package/.windsurf/rules/bmad-the-creator.md +0 -65
  115. package/.windsurf/rules/dev.md +0 -77
  116. package/.windsurf/rules/game-designer.md +0 -70
  117. package/.windsurf/rules/game-developer.md +0 -78
  118. package/.windsurf/rules/game-sm.md +0 -63
  119. package/.windsurf/rules/infra-devops-platform.md +0 -71
  120. package/.windsurf/rules/pm.md +0 -73
  121. package/.windsurf/rules/po.md +0 -75
  122. package/.windsurf/rules/qa.md +0 -62
  123. package/.windsurf/rules/sm.md +0 -63
  124. package/.windsurf/rules/ux-expert.md +0 -75
  125. package/docs/claude-code-guide.md +0 -121
  126. package/docs/cursor-guide.md +0 -131
  127. package/docs/expansion-pack-ideas.md +0 -121
  128. package/docs/roo-code-guide.md +0 -142
  129. package/docs/windsurf-guide.md +0 -129
  130. package/test-ide-paths.js +0 -41
  131. /package/dist/expansion-packs/{expansion-creator → bmad-creator-tools}/agents/bmad-the-creator.txt +0 -0
@@ -16,6 +16,20 @@ async function initializeModules() {
16
16
  }
17
17
 
18
18
  class Installer {
19
+ async getCoreVersion() {
20
+ const yaml = require("js-yaml");
21
+ const fs = require("fs-extra");
22
+ const coreConfigPath = path.join(__dirname, "../../../bmad-core/core-config.yml");
23
+ try {
24
+ const coreConfigContent = await fs.readFile(coreConfigPath, "utf8");
25
+ const coreConfig = yaml.load(coreConfigContent);
26
+ return coreConfig.version || "unknown";
27
+ } catch (error) {
28
+ console.warn("Could not read version from core-config.yml, using 'unknown'");
29
+ return "unknown";
30
+ }
31
+ }
32
+
19
33
  async install(config) {
20
34
  // Initialize ES modules
21
35
  await initializeModules();
@@ -161,6 +175,7 @@ class Installer {
161
175
  hasBmadCore: false,
162
176
  hasOtherFiles: false,
163
177
  manifest: null,
178
+ expansionPacks: {},
164
179
  };
165
180
 
166
181
  // Check if directory exists
@@ -209,10 +224,14 @@ class Installer {
209
224
  state.hasOtherFiles = true;
210
225
  }
211
226
 
227
+ // Check for expansion packs (folders starting with .)
228
+ const expansionPacks = await this.detectExpansionPacks(installDir);
229
+ state.expansionPacks = expansionPacks;
230
+
212
231
  return state; // clean install
213
232
  }
214
233
 
215
- async performFreshInstall(config, installDir, spinner) {
234
+ async performFreshInstall(config, installDir, spinner, options = {}) {
216
235
  // Ensure modules are initialized
217
236
  await initializeModules();
218
237
  spinner.text = "Installing BMAD Method...";
@@ -328,41 +347,13 @@ class Installer {
328
347
  const commonFiles = await this.copyCommonItems(installDir, ".bmad-core", spinner);
329
348
  files.push(...commonFiles);
330
349
  } else if (config.installType === "expansion-only") {
331
- // Expansion-only installation - create minimal .bmad-core structure
332
- spinner.text = "Creating minimal .bmad-core structure for expansion packs...";
333
-
334
- const bmadCoreDestDir = path.join(installDir, ".bmad-core");
335
- await fileManager.ensureDirectory(bmadCoreDestDir);
336
-
337
- // Create basic directory structure
338
- const dirs = ['agents', 'agent-teams', 'templates', 'tasks', 'checklists', 'workflows', 'data', 'utils', 'schemas'];
339
- for (const dir of dirs) {
340
- await fileManager.ensureDirectory(path.join(bmadCoreDestDir, dir));
341
- }
342
-
343
- // Copy minimal required files (schemas, utils, etc.)
344
- const sourceBase = configLoader.getBmadCorePath();
345
- const essentialFiles = [
346
- 'schemas/**/*',
347
- 'utils/**/*'
348
- ];
349
-
350
- for (const pattern of essentialFiles) {
351
- const copiedFiles = await fileManager.copyGlobPattern(
352
- pattern,
353
- sourceBase,
354
- bmadCoreDestDir
355
- );
356
- files.push(...copiedFiles.map(f => `.bmad-core/${f}`));
357
- }
358
-
359
- // Copy common/ items to .bmad-core
360
- spinner.text = "Copying common utilities...";
361
- await this.copyCommonItems(installDir, ".bmad-core", spinner);
350
+ // Expansion-only installation - DO NOT create .bmad-core
351
+ // Only install expansion packs
352
+ spinner.text = "Installing expansion packs only...";
362
353
  }
363
354
 
364
355
  // Install expansion packs if requested
365
- const expansionFiles = await this.installExpansionPacks(installDir, config.expansionPacks, spinner);
356
+ const expansionFiles = await this.installExpansionPacks(installDir, config.expansionPacks, spinner, config);
366
357
  files.push(...expansionFiles);
367
358
 
368
359
  // Install web bundles if requested
@@ -385,12 +376,14 @@ class Installer {
385
376
  }
386
377
  }
387
378
 
388
- // Create manifest
389
- spinner.text = "Creating installation manifest...";
390
- await fileManager.createManifest(installDir, config, files);
379
+ // Create manifest (skip for expansion-only installations)
380
+ if (config.installType !== "expansion-only") {
381
+ spinner.text = "Creating installation manifest...";
382
+ await fileManager.createManifest(installDir, config, files);
383
+ }
391
384
 
392
385
  spinner.succeed("Installation complete!");
393
- this.showSuccessMessage(config, installDir);
386
+ this.showSuccessMessage(config, installDir, options);
394
387
  }
395
388
 
396
389
  async handleExistingV4Installation(config, installDir, state, spinner) {
@@ -398,33 +391,137 @@ class Installer {
398
391
  await initializeModules();
399
392
  spinner.stop();
400
393
 
394
+ const currentVersion = state.manifest.version;
395
+ const newVersion = await this.getCoreVersion();
396
+ const versionCompare = this.compareVersions(currentVersion, newVersion);
397
+
401
398
  console.log(chalk.yellow("\n🔍 Found existing BMAD v4 installation"));
402
399
  console.log(` Directory: ${installDir}`);
403
- console.log(` Version: ${state.manifest.version}`);
400
+ console.log(` Current version: ${currentVersion}`);
401
+ console.log(` Available version: ${newVersion}`);
404
402
  console.log(
405
403
  ` Installed: ${new Date(
406
404
  state.manifest.installed_at
407
405
  ).toLocaleDateString()}`
408
406
  );
409
407
 
408
+ // Check file integrity
409
+ spinner.start("Checking installation integrity...");
410
+ const integrity = await fileManager.checkFileIntegrity(installDir, state.manifest);
411
+ spinner.stop();
412
+
413
+ const hasMissingFiles = integrity.missing.length > 0;
414
+ const hasModifiedFiles = integrity.modified.length > 0;
415
+ const hasIntegrityIssues = hasMissingFiles || hasModifiedFiles;
416
+
417
+ if (hasIntegrityIssues) {
418
+ console.log(chalk.red("\n⚠️ Installation issues detected:"));
419
+ if (hasMissingFiles) {
420
+ console.log(chalk.red(` Missing files: ${integrity.missing.length}`));
421
+ if (integrity.missing.length <= 5) {
422
+ integrity.missing.forEach(file => console.log(chalk.dim(` - ${file}`)));
423
+ }
424
+ }
425
+ if (hasModifiedFiles) {
426
+ console.log(chalk.yellow(` Modified files: ${integrity.modified.length}`));
427
+ if (integrity.modified.length <= 5) {
428
+ integrity.modified.forEach(file => console.log(chalk.dim(` - ${file}`)));
429
+ }
430
+ }
431
+ }
432
+
433
+ // Show existing expansion packs
434
+ if (Object.keys(state.expansionPacks).length > 0) {
435
+ console.log(chalk.cyan("\n📦 Installed expansion packs:"));
436
+ for (const [packId, packInfo] of Object.entries(state.expansionPacks)) {
437
+ if (packInfo.hasManifest && packInfo.manifest) {
438
+ console.log(` - ${packId} (v${packInfo.manifest.version || 'unknown'})`);
439
+ } else {
440
+ console.log(` - ${packId} (no manifest)`);
441
+ }
442
+ }
443
+ }
444
+
445
+ let choices = [];
446
+
447
+ if (versionCompare < 0) {
448
+ console.log(chalk.cyan("\n⬆️ Upgrade available for BMAD core"));
449
+ choices.push({ name: `Upgrade BMAD core (v${currentVersion} → v${newVersion})`, value: "upgrade" });
450
+ } else if (versionCompare === 0) {
451
+ if (hasIntegrityIssues) {
452
+ // Offer repair option when files are missing or modified
453
+ choices.push({
454
+ name: "Repair installation (restore missing/modified files)",
455
+ value: "repair"
456
+ });
457
+ }
458
+ console.log(chalk.yellow("\n⚠️ Same version already installed"));
459
+ choices.push({ name: `Force reinstall BMAD core (v${currentVersion} - reinstall)`, value: "reinstall" });
460
+ } else {
461
+ console.log(chalk.yellow("\n⬇️ Installed version is newer than available"));
462
+ choices.push({ name: `Downgrade BMAD core (v${currentVersion} → v${newVersion})`, value: "reinstall" });
463
+ }
464
+
465
+ choices.push(
466
+ { name: "Add/update expansion packs only", value: "expansions" },
467
+ { name: "Cancel", value: "cancel" }
468
+ );
469
+
410
470
  const { action } = await inquirer.prompt([
411
471
  {
412
472
  type: "list",
413
473
  name: "action",
414
474
  message: "What would you like to do?",
415
- choices: [
416
- { name: "Update existing installation", value: "update" },
417
- { name: "Reinstall (overwrite)", value: "reinstall" },
418
- { name: "Cancel", value: "cancel" },
419
- ],
475
+ choices: choices,
420
476
  },
421
477
  ]);
422
478
 
423
479
  switch (action) {
424
- case "update":
480
+ case "upgrade":
425
481
  return await this.performUpdate(config, installDir, state.manifest, spinner);
482
+ case "repair":
483
+ // For repair, restore missing/modified files while backing up modified ones
484
+ return await this.performRepair(config, installDir, state.manifest, integrity, spinner);
426
485
  case "reinstall":
486
+ // For reinstall, don't check for modifications - just overwrite
427
487
  return await this.performReinstall(config, installDir, spinner);
488
+ case "expansions":
489
+ // Ask which expansion packs to install
490
+ const availableExpansionPacks = await this.getAvailableExpansionPacks();
491
+
492
+ if (availableExpansionPacks.length === 0) {
493
+ console.log(chalk.yellow("No expansion packs available."));
494
+ return;
495
+ }
496
+
497
+ const { selectedPacks } = await inquirer.prompt([
498
+ {
499
+ type: 'checkbox',
500
+ name: 'selectedPacks',
501
+ message: 'Select expansion packs to install/update:',
502
+ choices: availableExpansionPacks.map(pack => ({
503
+ name: `${pack.name} v${pack.version} - ${pack.description}`,
504
+ value: pack.id,
505
+ checked: state.expansionPacks[pack.id] !== undefined
506
+ }))
507
+ }
508
+ ]);
509
+
510
+ if (selectedPacks.length === 0) {
511
+ console.log(chalk.yellow("No expansion packs selected."));
512
+ return;
513
+ }
514
+
515
+ spinner.start("Installing expansion packs...");
516
+ const expansionFiles = await this.installExpansionPacks(installDir, selectedPacks, spinner, { ides: config.ides || [] });
517
+ spinner.succeed("Expansion packs installed successfully!");
518
+
519
+ console.log(chalk.green("\n✓ Installation complete!"));
520
+ console.log(chalk.green(`✓ Expansion packs installed/updated:`));
521
+ for (const packId of selectedPacks) {
522
+ console.log(chalk.green(` - ${packId} → .${packId}/`));
523
+ }
524
+ return;
428
525
  case "cancel":
429
526
  console.log("Installation cancelled.");
430
527
  return;
@@ -525,12 +622,20 @@ class Installer {
525
622
  spinner.start("Checking for updates...");
526
623
 
527
624
  try {
528
- // Check for modified files
529
- spinner.text = "Checking for modified files...";
530
- const modifiedFiles = await fileManager.checkModifiedFiles(
531
- installDir,
532
- manifest
533
- );
625
+ // Get current and new versions
626
+ const currentVersion = manifest.version;
627
+ const newVersion = await this.getCoreVersion();
628
+ const versionCompare = this.compareVersions(currentVersion, newVersion);
629
+
630
+ // Only check for modified files if it's an actual version upgrade
631
+ let modifiedFiles = [];
632
+ if (versionCompare !== 0) {
633
+ spinner.text = "Checking for modified files...";
634
+ modifiedFiles = await fileManager.checkModifiedFiles(
635
+ installDir,
636
+ manifest
637
+ );
638
+ }
534
639
 
535
640
  if (modifiedFiles.length > 0) {
536
641
  spinner.warn("Found modified files");
@@ -570,35 +675,117 @@ class Installer {
570
675
  }
571
676
 
572
677
  // Perform update by re-running installation
573
- spinner.text = "Updating files...";
678
+ spinner.text = versionCompare === 0 ? "Reinstalling files..." : "Updating files...";
574
679
  const config = {
575
680
  installType: manifest.install_type,
576
681
  agent: manifest.agent,
577
682
  directory: installDir,
578
- ide: newConfig?.ide || manifest.ide_setup, // Use new IDE choice if provided
579
683
  ides: newConfig?.ides || manifest.ides_setup || [],
580
684
  };
581
685
 
582
- await this.performFreshInstall(config, installDir, spinner);
686
+ await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
583
687
  } catch (error) {
584
688
  spinner.fail("Update failed");
585
689
  throw error;
586
690
  }
587
691
  }
588
692
 
693
+ async performRepair(config, installDir, manifest, integrity, spinner) {
694
+ spinner.start("Preparing to repair installation...");
695
+
696
+ try {
697
+ // Back up modified files
698
+ if (integrity.modified.length > 0) {
699
+ spinner.text = "Backing up modified files...";
700
+ for (const file of integrity.modified) {
701
+ const filePath = path.join(installDir, file);
702
+ if (await fileManager.pathExists(filePath)) {
703
+ const backupPath = await fileManager.backupFile(filePath);
704
+ console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
705
+ }
706
+ }
707
+ }
708
+
709
+ // Restore missing and modified files
710
+ spinner.text = "Restoring files...";
711
+ const sourceBase = configLoader.getBmadCorePath();
712
+ const filesToRestore = [...integrity.missing, ...integrity.modified];
713
+
714
+ for (const file of filesToRestore) {
715
+ // Skip the manifest file itself
716
+ if (file.endsWith('install-manifest.yml')) continue;
717
+
718
+ const relativePath = file.replace('.bmad-core/', '');
719
+ const destPath = path.join(installDir, file);
720
+
721
+ // Check if this is a common/ file that needs special processing
722
+ const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
723
+ const commonSourcePath = path.join(commonBase, 'common', relativePath);
724
+
725
+ if (await fileManager.pathExists(commonSourcePath)) {
726
+ // This is a common/ file - needs template processing
727
+ const fs = require('fs').promises;
728
+ const content = await fs.readFile(commonSourcePath, 'utf8');
729
+ const updatedContent = content.replace(/\{root\}/g, '.bmad-core');
730
+ await fileManager.ensureDirectory(path.dirname(destPath));
731
+ await fs.writeFile(destPath, updatedContent, 'utf8');
732
+ spinner.text = `Restored: ${file}`;
733
+ } else {
734
+ // Regular file from bmad-core
735
+ const sourcePath = path.join(sourceBase, relativePath);
736
+ if (await fileManager.pathExists(sourcePath)) {
737
+ await fileManager.copyFile(sourcePath, destPath);
738
+ spinner.text = `Restored: ${file}`;
739
+ } else {
740
+ console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
741
+ }
742
+ }
743
+ }
744
+
745
+ spinner.succeed("Repair completed successfully!");
746
+
747
+ // Show summary
748
+ console.log(chalk.green("\n✓ Installation repaired!"));
749
+ if (integrity.missing.length > 0) {
750
+ console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
751
+ }
752
+ if (integrity.modified.length > 0) {
753
+ console.log(chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`));
754
+ }
755
+
756
+ // Warning for Cursor custom modes if agents were repaired
757
+ const ides = manifest.ides_setup || [];
758
+ if (ides.includes('cursor')) {
759
+ console.log(chalk.yellow.bold("\n⚠️ IMPORTANT: Cursor Custom Modes Update Required"));
760
+ console.log(chalk.yellow("Since agent files have been repaired, you need to manually update your Cursor custom modes:"));
761
+ console.log(chalk.yellow("1. Open Cursor Settings (Cmd/Ctrl + ,)"));
762
+ console.log(chalk.yellow("2. Go to: Features > Cursor Tab > Custom Modes"));
763
+ console.log(chalk.yellow("3. Update each custom mode with the latest agent templates from:"));
764
+ console.log(chalk.yellow(` ${path.join(installDir, '.bmad-core', 'agents')}`));
765
+ console.log(chalk.yellow("4. Copy the full content of each agent file into the corresponding custom mode"));
766
+ }
767
+
768
+ } catch (error) {
769
+ spinner.fail("Repair failed");
770
+ throw error;
771
+ }
772
+ }
773
+
589
774
  async performReinstall(config, installDir, spinner) {
590
- spinner.start("Reinstalling BMAD Method...");
775
+ spinner.start("Preparing to reinstall BMAD Method...");
591
776
 
592
777
  // Remove existing .bmad-core
593
778
  const bmadCorePath = path.join(installDir, ".bmad-core");
594
779
  if (await fileManager.pathExists(bmadCorePath)) {
780
+ spinner.text = "Removing existing installation...";
595
781
  await fileManager.removeDirectory(bmadCorePath);
596
782
  }
597
-
598
- return await this.performFreshInstall(config, installDir, spinner);
783
+
784
+ spinner.text = "Installing fresh copy...";
785
+ return await this.performFreshInstall(config, installDir, spinner, { isUpdate: true });
599
786
  }
600
787
 
601
- showSuccessMessage(config, installDir) {
788
+ showSuccessMessage(config, installDir, options = {}) {
602
789
  console.log(chalk.green("\n✓ BMAD Method installed successfully!\n"));
603
790
 
604
791
  const ides = config.ides || (config.ide ? [config.ide] : []);
@@ -622,7 +809,9 @@ class Installer {
622
809
 
623
810
  // Information about installation components
624
811
  console.log(chalk.bold("\n🎯 Installation Summary:"));
625
- console.log(chalk.green("✓ .bmad-core framework installed with all agents and workflows"));
812
+ if (config.installType !== "expansion-only") {
813
+ console.log(chalk.green("✓ .bmad-core framework installed with all agents and workflows"));
814
+ }
626
815
 
627
816
  if (config.expansionPacks && config.expansionPacks.length > 0) {
628
817
  console.log(chalk.green(`✓ Expansion packs installed:`));
@@ -668,6 +857,17 @@ class Installer {
668
857
  chalk.dim("Need everything? Run: npx bmad-method install --full")
669
858
  );
670
859
  }
860
+
861
+ // Warning for Cursor custom modes if agents were updated
862
+ if (options.isUpdate && ides.includes('cursor')) {
863
+ console.log(chalk.yellow.bold("\n⚠️ IMPORTANT: Cursor Custom Modes Update Required"));
864
+ console.log(chalk.yellow("Since agents have been updated, you need to manually update your Cursor custom modes:"));
865
+ console.log(chalk.yellow("1. Open Cursor Settings (Cmd/Ctrl + ,)"));
866
+ console.log(chalk.yellow("2. Go to: Features > Cursor Tab > Custom Modes"));
867
+ console.log(chalk.yellow("3. Update each custom mode with the latest agent templates from:"));
868
+ console.log(chalk.yellow(` ${path.join(installDir, '.bmad-core', 'agents')}`));
869
+ console.log(chalk.yellow("4. Copy the full content of each agent file into the corresponding custom mode"));
870
+ }
671
871
  }
672
872
 
673
873
  // Legacy method for backward compatibility
@@ -767,8 +967,8 @@ class Installer {
767
967
  console.log(` Agent: ${manifest.agent}`);
768
968
  }
769
969
 
770
- if (manifest.ide_setup) {
771
- console.log(` IDE Setup: ${manifest.ide_setup}`);
970
+ if (manifest.ides_setup && manifest.ides_setup.length > 0) {
971
+ console.log(` IDE Setup: ${manifest.ides_setup.join(', ')}`);
772
972
  }
773
973
 
774
974
  console.log(` Total Files: ${manifest.files.length}`);
@@ -797,7 +997,7 @@ class Installer {
797
997
  return configLoader.getAvailableTeams();
798
998
  }
799
999
 
800
- async installExpansionPacks(installDir, selectedPacks, spinner) {
1000
+ async installExpansionPacks(installDir, selectedPacks, spinner, config = {}) {
801
1001
  if (!selectedPacks || selectedPacks.length === 0) {
802
1002
  return [];
803
1003
  }
@@ -816,11 +1016,112 @@ class Installer {
816
1016
  console.warn(`Expansion pack ${packId} not found, skipping...`);
817
1017
  continue;
818
1018
  }
1019
+
1020
+ // Check if expansion pack already exists
1021
+ let expansionDotFolder = path.join(installDir, `.${packId}`);
1022
+ const existingManifestPath = path.join(expansionDotFolder, 'install-manifest.yml');
1023
+
1024
+ if (await fileManager.pathExists(existingManifestPath)) {
1025
+ spinner.stop();
1026
+ const existingManifest = await fileManager.readExpansionPackManifest(installDir, packId);
1027
+
1028
+ console.log(chalk.yellow(`\n🔍 Found existing ${pack.name} installation`));
1029
+ console.log(` Current version: ${existingManifest.version || 'unknown'}`);
1030
+ console.log(` New version: ${pack.version}`);
1031
+
1032
+ // Check integrity of existing expansion pack
1033
+ const packIntegrity = await fileManager.checkFileIntegrity(installDir, existingManifest);
1034
+ const hasPackIntegrityIssues = packIntegrity.missing.length > 0 || packIntegrity.modified.length > 0;
1035
+
1036
+ if (hasPackIntegrityIssues) {
1037
+ console.log(chalk.red(" ⚠️ Installation issues detected:"));
1038
+ if (packIntegrity.missing.length > 0) {
1039
+ console.log(chalk.red(` Missing files: ${packIntegrity.missing.length}`));
1040
+ }
1041
+ if (packIntegrity.modified.length > 0) {
1042
+ console.log(chalk.yellow(` Modified files: ${packIntegrity.modified.length}`));
1043
+ }
1044
+ }
1045
+
1046
+ const versionCompare = this.compareVersions(existingManifest.version || '0.0.0', pack.version);
1047
+
1048
+ if (versionCompare === 0) {
1049
+ console.log(chalk.yellow(' ⚠️ Same version already installed'));
1050
+
1051
+ const choices = [];
1052
+ if (hasPackIntegrityIssues) {
1053
+ choices.push({ name: 'Repair (restore missing/modified files)', value: 'repair' });
1054
+ }
1055
+ choices.push(
1056
+ { name: 'Force reinstall (overwrite)', value: 'overwrite' },
1057
+ { name: 'Skip this expansion pack', value: 'skip' },
1058
+ { name: 'Cancel installation', value: 'cancel' }
1059
+ );
1060
+
1061
+ const { action } = await inquirer.prompt([{
1062
+ type: 'list',
1063
+ name: 'action',
1064
+ message: `${pack.name} v${pack.version} is already installed. What would you like to do?`,
1065
+ choices: choices
1066
+ }]);
1067
+
1068
+ if (action === 'skip') {
1069
+ spinner.start();
1070
+ continue;
1071
+ } else if (action === 'cancel') {
1072
+ console.log(chalk.red('Installation cancelled.'));
1073
+ process.exit(0);
1074
+ } else if (action === 'repair') {
1075
+ // Repair the expansion pack
1076
+ await this.repairExpansionPack(installDir, packId, pack, packIntegrity, spinner);
1077
+ continue;
1078
+ }
1079
+ } else if (versionCompare < 0) {
1080
+ console.log(chalk.cyan(' ⬆️ Upgrade available'));
1081
+
1082
+ const { proceed } = await inquirer.prompt([{
1083
+ type: 'confirm',
1084
+ name: 'proceed',
1085
+ message: `Upgrade ${pack.name} from v${existingManifest.version} to v${pack.version}?`,
1086
+ default: true
1087
+ }]);
1088
+
1089
+ if (!proceed) {
1090
+ spinner.start();
1091
+ continue;
1092
+ }
1093
+ } else {
1094
+ console.log(chalk.yellow(' ⬇️ Installed version is newer than available version'));
1095
+
1096
+ const { action } = await inquirer.prompt([{
1097
+ type: 'list',
1098
+ name: 'action',
1099
+ message: 'What would you like to do?',
1100
+ choices: [
1101
+ { name: 'Keep current version', value: 'skip' },
1102
+ { name: 'Downgrade to available version', value: 'downgrade' },
1103
+ { name: 'Cancel installation', value: 'cancel' }
1104
+ ]
1105
+ }]);
1106
+
1107
+ if (action === 'skip') {
1108
+ spinner.start();
1109
+ continue;
1110
+ } else if (action === 'cancel') {
1111
+ console.log(chalk.red('Installation cancelled.'));
1112
+ process.exit(0);
1113
+ }
1114
+ }
1115
+
1116
+ // If we get here, we're proceeding with installation
1117
+ spinner.start(`Removing old ${pack.name} installation...`);
1118
+ await fileManager.removeDirectory(expansionDotFolder);
1119
+ }
819
1120
 
820
1121
  const expansionPackDir = pack.packPath;
821
1122
 
822
- // Create dedicated dot folder for this expansion pack
823
- const expansionDotFolder = path.join(installDir, `.${packId}`);
1123
+ // Ensure dedicated dot folder exists for this expansion pack
1124
+ expansionDotFolder = path.join(installDir, `.${packId}`);
824
1125
  await fileManager.ensureDirectory(expansionDotFolder);
825
1126
 
826
1127
  // Define the folders to copy from expansion packs
@@ -888,9 +1189,28 @@ class Installer {
888
1189
  // Check and resolve core agents referenced by teams
889
1190
  await this.resolveExpansionPackCoreAgents(installDir, expansionDotFolder, packId, spinner);
890
1191
 
1192
+ // Create manifest for this expansion pack
1193
+ spinner.text = `Creating manifest for ${packId}...`;
1194
+ const expansionConfig = {
1195
+ installType: 'expansion-pack',
1196
+ expansionPackId: packId,
1197
+ expansionPackName: pack.name,
1198
+ expansionPackVersion: pack.version,
1199
+ ides: config.ides || [] // Use ides_setup instead of ide_setup
1200
+ };
1201
+
1202
+ // Get all files installed in this expansion pack
1203
+ const expansionPackFiles = glob.sync('**/*', {
1204
+ cwd: expansionDotFolder,
1205
+ nodir: true
1206
+ }).map(f => path.join(`.${packId}`, f));
1207
+
1208
+ await fileManager.createExpansionPackManifest(installDir, packId, expansionConfig, expansionPackFiles);
1209
+
891
1210
  console.log(chalk.green(`✓ Installed expansion pack: ${pack.name} to ${`.${packId}`}`));
892
1211
  } catch (error) {
893
1212
  console.error(chalk.red(`Failed to install expansion pack ${packId}: ${error.message}`));
1213
+ console.error(chalk.red(`Stack trace: ${error.stack}`));
894
1214
  }
895
1215
  }
896
1216
 
@@ -1034,7 +1354,8 @@ class Installer {
1034
1354
  console.log(chalk.dim(` Added agent dependency: ${depType}/${depFileName}`));
1035
1355
  } else {
1036
1356
  // Try common folder
1037
- const commonDepPath = path.join(this.rootDir, 'common', depType, depFileName);
1357
+ const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
1358
+ const commonDepPath = path.join(sourceBase, 'common', depType, depFileName);
1038
1359
  if (await fileManager.pathExists(commonDepPath)) {
1039
1360
  const destDepPath = path.join(expansionDotFolder, depType, depFileName);
1040
1361
  await fileManager.copyFile(commonDepPath, destDepPath);
@@ -1157,6 +1478,9 @@ class Installer {
1157
1478
  }
1158
1479
 
1159
1480
  async copyCommonItems(installDir, targetSubdir, spinner) {
1481
+ // Ensure modules are initialized
1482
+ await initializeModules();
1483
+
1160
1484
  const glob = require('glob');
1161
1485
  const fs = require('fs').promises;
1162
1486
  const sourceBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename)))); // Go up to project root
@@ -1198,6 +1522,133 @@ class Installer {
1198
1522
  return copiedFiles;
1199
1523
  }
1200
1524
 
1525
+ async detectExpansionPacks(installDir) {
1526
+ const expansionPacks = {};
1527
+ const glob = require("glob");
1528
+
1529
+ // Find all dot folders that might be expansion packs
1530
+ const dotFolders = glob.sync(".*", {
1531
+ cwd: installDir,
1532
+ ignore: [".git", ".git/**", ".bmad-core", ".bmad-core/**"],
1533
+ });
1534
+
1535
+ for (const folder of dotFolders) {
1536
+ const folderPath = path.join(installDir, folder);
1537
+ const stats = await fileManager.pathExists(folderPath);
1538
+
1539
+ if (stats) {
1540
+ // Check if it has a manifest
1541
+ const manifestPath = path.join(folderPath, "install-manifest.yml");
1542
+ if (await fileManager.pathExists(manifestPath)) {
1543
+ const manifest = await fileManager.readExpansionPackManifest(installDir, folder.substring(1));
1544
+ if (manifest) {
1545
+ expansionPacks[folder.substring(1)] = {
1546
+ path: folderPath,
1547
+ manifest: manifest,
1548
+ hasManifest: true
1549
+ };
1550
+ }
1551
+ } else {
1552
+ // Check if it has a config.yml (expansion pack without manifest)
1553
+ const configPath = path.join(folderPath, "config.yml");
1554
+ if (await fileManager.pathExists(configPath)) {
1555
+ expansionPacks[folder.substring(1)] = {
1556
+ path: folderPath,
1557
+ manifest: null,
1558
+ hasManifest: false
1559
+ };
1560
+ }
1561
+ }
1562
+ }
1563
+ }
1564
+
1565
+ return expansionPacks;
1566
+ }
1567
+
1568
+ async repairExpansionPack(installDir, packId, pack, integrity, spinner) {
1569
+ spinner.start(`Repairing ${pack.name}...`);
1570
+
1571
+ try {
1572
+ const expansionDotFolder = path.join(installDir, `.${packId}`);
1573
+
1574
+ // Back up modified files
1575
+ if (integrity.modified.length > 0) {
1576
+ spinner.text = "Backing up modified files...";
1577
+ for (const file of integrity.modified) {
1578
+ const filePath = path.join(installDir, file);
1579
+ if (await fileManager.pathExists(filePath)) {
1580
+ const backupPath = await fileManager.backupFile(filePath);
1581
+ console.log(chalk.dim(` Backed up: ${file} → ${path.basename(backupPath)}`));
1582
+ }
1583
+ }
1584
+ }
1585
+
1586
+ // Restore missing and modified files
1587
+ spinner.text = "Restoring files...";
1588
+ const filesToRestore = [...integrity.missing, ...integrity.modified];
1589
+
1590
+ for (const file of filesToRestore) {
1591
+ // Skip the manifest file itself
1592
+ if (file.endsWith('install-manifest.yml')) continue;
1593
+
1594
+ const relativePath = file.replace(`.${packId}/`, '');
1595
+ const sourcePath = path.join(pack.packPath, relativePath);
1596
+ const destPath = path.join(installDir, file);
1597
+
1598
+ // Check if this is a common/ file that needs special processing
1599
+ const commonBase = path.dirname(path.dirname(path.dirname(path.dirname(__filename))));
1600
+ const commonSourcePath = path.join(commonBase, 'common', relativePath);
1601
+
1602
+ if (await fileManager.pathExists(commonSourcePath)) {
1603
+ // This is a common/ file - needs template processing
1604
+ const fs = require('fs').promises;
1605
+ const content = await fs.readFile(commonSourcePath, 'utf8');
1606
+ const updatedContent = content.replace(/\{root\}/g, `.${packId}`);
1607
+ await fileManager.ensureDirectory(path.dirname(destPath));
1608
+ await fs.writeFile(destPath, updatedContent, 'utf8');
1609
+ spinner.text = `Restored: ${file}`;
1610
+ } else if (await fileManager.pathExists(sourcePath)) {
1611
+ // Regular file from expansion pack
1612
+ await fileManager.copyFile(sourcePath, destPath);
1613
+ spinner.text = `Restored: ${file}`;
1614
+ } else {
1615
+ console.warn(chalk.yellow(` Warning: Source file not found: ${file}`));
1616
+ }
1617
+ }
1618
+
1619
+ spinner.succeed(`${pack.name} repaired successfully!`);
1620
+
1621
+ // Show summary
1622
+ console.log(chalk.green(`\n✓ ${pack.name} repaired!`));
1623
+ if (integrity.missing.length > 0) {
1624
+ console.log(chalk.green(` Restored ${integrity.missing.length} missing files`));
1625
+ }
1626
+ if (integrity.modified.length > 0) {
1627
+ console.log(chalk.green(` Restored ${integrity.modified.length} modified files (backups created)`));
1628
+ }
1629
+
1630
+ } catch (error) {
1631
+ spinner.fail(`Failed to repair ${pack.name}`);
1632
+ console.error(chalk.red(`Error: ${error.message}`));
1633
+ }
1634
+ }
1635
+
1636
+ compareVersions(v1, v2) {
1637
+ // Simple semver comparison
1638
+ const parts1 = v1.split('.').map(Number);
1639
+ const parts2 = v2.split('.').map(Number);
1640
+
1641
+ for (let i = 0; i < 3; i++) {
1642
+ const part1 = parts1[i] || 0;
1643
+ const part2 = parts2[i] || 0;
1644
+
1645
+ if (part1 > part2) return 1;
1646
+ if (part1 < part2) return -1;
1647
+ }
1648
+
1649
+ return 0;
1650
+ }
1651
+
1201
1652
  async findInstallation() {
1202
1653
  // Look for .bmad-core in current directory or parent directories
1203
1654
  let currentDir = process.cwd();