bmad-method 4.20.0 → 4.21.1
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/CHANGELOG.md +19 -0
- package/CONTRIBUTING.md +38 -4
- package/README.md +42 -18
- package/bmad-core/core-config.yml +1 -0
- package/dist/agents/dev.txt +1 -1
- package/dist/teams/team-all.txt +1 -1
- package/dist/teams/team-ide-minimal.txt +1 -1
- package/docs/how-to-contribute-with-pull-requests.md +24 -7
- package/expansion-packs/bmad-2d-phaser-game-dev/config.yml +4 -2
- package/expansion-packs/bmad-creator-tools/config.yml +1 -1
- package/expansion-packs/bmad-infrastructure-devops/config.yml +5 -2
- package/package.json +15 -1
- package/tools/bump-all-versions.js +107 -0
- package/tools/bump-core-version.js +57 -0
- package/tools/bump-expansion-version.js +78 -0
- package/tools/installer/bin/bmad.js +79 -158
- package/tools/installer/lib/file-manager.js +90 -2
- package/tools/installer/lib/installer.js +515 -64
- package/tools/installer/package.json +1 -1
- package/tools/update-expansion-version.js +54 -0
- package/test-ide-paths.js +0 -41
|
@@ -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
|
|
332
|
-
|
|
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
|
-
|
|
390
|
-
|
|
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(`
|
|
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 "
|
|
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
|
-
//
|
|
529
|
-
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
771
|
-
console.log(` IDE Setup: ${manifest.
|
|
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
|
-
//
|
|
823
|
-
|
|
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
|
|
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();
|