bmad-method 4.37.0 → 5.0.0-beta.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.
Files changed (47) hide show
  1. package/.github/workflows/promote-to-stable.yml +144 -0
  2. package/CHANGELOG.md +16 -9
  3. package/bmad-core/agents/qa.md +37 -18
  4. package/bmad-core/data/test-levels-framework.md +146 -0
  5. package/bmad-core/data/test-priorities-matrix.md +172 -0
  6. package/bmad-core/tasks/nfr-assess.md +343 -0
  7. package/bmad-core/tasks/qa-gate.md +159 -0
  8. package/bmad-core/tasks/review-story.md +234 -74
  9. package/bmad-core/tasks/risk-profile.md +353 -0
  10. package/bmad-core/tasks/test-design.md +174 -0
  11. package/bmad-core/tasks/trace-requirements.md +264 -0
  12. package/bmad-core/templates/qa-gate-tmpl.yaml +102 -0
  13. package/dist/agents/analyst.txt +20 -26
  14. package/dist/agents/architect.txt +14 -35
  15. package/dist/agents/bmad-master.txt +40 -70
  16. package/dist/agents/bmad-orchestrator.txt +28 -5
  17. package/dist/agents/dev.txt +0 -14
  18. package/dist/agents/pm.txt +0 -25
  19. package/dist/agents/po.txt +0 -18
  20. package/dist/agents/qa.txt +2079 -135
  21. package/dist/agents/sm.txt +0 -10
  22. package/dist/agents/ux-expert.txt +0 -7
  23. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +0 -37
  24. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +3 -12
  25. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +0 -7
  26. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +44 -90
  27. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +14 -49
  28. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +0 -46
  29. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.txt +0 -15
  30. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +0 -17
  31. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +38 -142
  32. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +0 -2
  33. package/dist/teams/team-all.txt +2181 -261
  34. package/dist/teams/team-fullstack.txt +43 -57
  35. package/dist/teams/team-ide-minimal.txt +2064 -125
  36. package/dist/teams/team-no-ui.txt +43 -57
  37. package/docs/enhanced-ide-development-workflow.md +220 -15
  38. package/docs/user-guide.md +271 -18
  39. package/docs/working-in-the-brownfield.md +264 -31
  40. package/package.json +1 -1
  41. package/tools/installer/bin/bmad.js +33 -32
  42. package/tools/installer/config/install.config.yaml +11 -1
  43. package/tools/installer/lib/file-manager.js +1 -1
  44. package/tools/installer/lib/ide-base-setup.js +1 -1
  45. package/tools/installer/lib/ide-setup.js +197 -83
  46. package/tools/installer/lib/installer.js +3 -3
  47. package/tools/installer/package.json +1 -1
@@ -1,8 +1,8 @@
1
1
  const path = require("path");
2
2
  const fs = require("fs-extra");
3
3
  const yaml = require("js-yaml");
4
- const chalk = require("chalk");
5
- const inquirer = require("inquirer");
4
+ const chalk = require("chalk").default || require("chalk");
5
+ const inquirer = require("inquirer").default || require("inquirer");
6
6
  const fileManager = require("./file-manager");
7
7
  const configLoader = require("./config-loader");
8
8
  const { extractYamlFromAgent } = require("../../lib/yaml-utils");
@@ -17,7 +17,7 @@ class IdeSetup extends BaseIdeSetup {
17
17
 
18
18
  async loadIdeAgentConfig() {
19
19
  if (this.ideAgentConfig) return this.ideAgentConfig;
20
-
20
+
21
21
  try {
22
22
  const configPath = path.join(__dirname, '..', 'config', 'ide-agent-config.yaml');
23
23
  const configContent = await fs.readFile(configPath, 'utf8');
@@ -45,6 +45,8 @@ class IdeSetup extends BaseIdeSetup {
45
45
  return this.setupCursor(installDir, selectedAgent);
46
46
  case "claude-code":
47
47
  return this.setupClaudeCode(installDir, selectedAgent);
48
+ case "crush":
49
+ return this.setupCrush(installDir, selectedAgent);
48
50
  case "windsurf":
49
51
  return this.setupWindsurf(installDir, selectedAgent);
50
52
  case "trae":
@@ -88,6 +90,30 @@ class IdeSetup extends BaseIdeSetup {
88
90
  return true;
89
91
  }
90
92
 
93
+ async setupCrush(installDir, selectedAgent) {
94
+ // Setup bmad-core commands
95
+ const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
96
+ const coreAgents = selectedAgent ? [selectedAgent] : await this.getCoreAgentIds(installDir);
97
+ const coreTasks = await this.getCoreTaskIds(installDir);
98
+ await this.setupCrushForPackage(installDir, "core", coreSlashPrefix, coreAgents, coreTasks, ".bmad-core");
99
+
100
+ // Setup expansion pack commands
101
+ const expansionPacks = await this.getInstalledExpansionPacks(installDir);
102
+ for (const packInfo of expansionPacks) {
103
+ const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
104
+ const packAgents = await this.getExpansionPackAgents(packInfo.path);
105
+ const packTasks = await this.getExpansionPackTasks(packInfo.path);
106
+
107
+ if (packAgents.length > 0 || packTasks.length > 0) {
108
+ // Use the actual directory name where the expansion pack is installed
109
+ const rootPath = path.relative(installDir, packInfo.path);
110
+ await this.setupCrushForPackage(installDir, packInfo.name, packSlashPrefix, packAgents, packTasks, rootPath);
111
+ }
112
+ }
113
+
114
+ return true;
115
+ }
116
+
91
117
  async setupClaudeCode(installDir, selectedAgent) {
92
118
  // Setup bmad-core commands
93
119
  const coreSlashPrefix = await this.getCoreSlashPrefix(installDir);
@@ -101,7 +127,7 @@ class IdeSetup extends BaseIdeSetup {
101
127
  const packSlashPrefix = await this.getExpansionPackSlashPrefix(packInfo.path);
102
128
  const packAgents = await this.getExpansionPackAgents(packInfo.path);
103
129
  const packTasks = await this.getExpansionPackTasks(packInfo.path);
104
-
130
+
105
131
  if (packAgents.length > 0 || packTasks.length > 0) {
106
132
  // Use the actual directory name where the expansion pack is installed
107
133
  const rootPath = path.relative(installDir, packInfo.path);
@@ -138,13 +164,13 @@ class IdeSetup extends BaseIdeSetup {
138
164
  // For core, use the normal search
139
165
  agentPath = await this.findAgentPath(agentId, installDir);
140
166
  }
141
-
167
+
142
168
  const commandPath = path.join(agentsDir, `${agentId}.md`);
143
169
 
144
170
  if (agentPath) {
145
171
  // Create command file with agent content
146
172
  let agentContent = await fileManager.readFile(agentPath);
147
-
173
+
148
174
  // Replace {root} placeholder with the appropriate root path for this context
149
175
  agentContent = agentContent.replace(/{root}/g, rootPath);
150
176
 
@@ -175,13 +201,13 @@ class IdeSetup extends BaseIdeSetup {
175
201
  // For core, use the normal search
176
202
  taskPath = await this.findTaskPath(taskId, installDir);
177
203
  }
178
-
204
+
179
205
  const commandPath = path.join(tasksDir, `${taskId}.md`);
180
206
 
181
207
  if (taskPath) {
182
208
  // Create command file with task content
183
209
  let taskContent = await fileManager.readFile(taskPath);
184
-
210
+
185
211
  // Replace {root} placeholder with the appropriate root path for this context
186
212
  taskContent = taskContent.replace(/{root}/g, rootPath);
187
213
 
@@ -200,6 +226,94 @@ class IdeSetup extends BaseIdeSetup {
200
226
  console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
201
227
  }
202
228
 
229
+ async setupCrushForPackage(installDir, packageName, slashPrefix, agentIds, taskIds, rootPath) {
230
+ const commandsBaseDir = path.join(installDir, ".crush", "commands", slashPrefix);
231
+ const agentsDir = path.join(commandsBaseDir, "agents");
232
+ const tasksDir = path.join(commandsBaseDir, "tasks");
233
+
234
+ // Ensure directories exist
235
+ await fileManager.ensureDirectory(agentsDir);
236
+ await fileManager.ensureDirectory(tasksDir);
237
+
238
+ // Setup agents
239
+ for (const agentId of agentIds) {
240
+ // Find the agent file - for expansion packs, prefer the expansion pack version
241
+ let agentPath;
242
+ if (packageName !== "core") {
243
+ // For expansion packs, first try to find the agent in the expansion pack directory
244
+ const expansionPackPath = path.join(installDir, rootPath, "agents", `${agentId}.md`);
245
+ if (await fileManager.pathExists(expansionPackPath)) {
246
+ agentPath = expansionPackPath;
247
+ } else {
248
+ // Fall back to core if not found in expansion pack
249
+ agentPath = await this.findAgentPath(agentId, installDir);
250
+ }
251
+ } else {
252
+ // For core, use the normal search
253
+ agentPath = await this.findAgentPath(agentId, installDir);
254
+ }
255
+
256
+ const commandPath = path.join(agentsDir, `${agentId}.md`);
257
+
258
+ if (agentPath) {
259
+ // Create command file with agent content
260
+ let agentContent = await fileManager.readFile(agentPath);
261
+
262
+ // Replace {root} placeholder with the appropriate root path for this context
263
+ agentContent = agentContent.replace(/{root}/g, rootPath);
264
+
265
+ // Add command header
266
+ let commandContent = `# /${agentId} Command\n\n`;
267
+ commandContent += `When this command is used, adopt the following agent persona:\n\n`;
268
+ commandContent += agentContent;
269
+
270
+ await fileManager.writeFile(commandPath, commandContent);
271
+ console.log(chalk.green(`āœ“ Created agent command: /${agentId}`));
272
+ }
273
+ }
274
+
275
+ // Setup tasks
276
+ for (const taskId of taskIds) {
277
+ // Find the task file - for expansion packs, prefer the expansion pack version
278
+ let taskPath;
279
+ if (packageName !== "core") {
280
+ // For expansion packs, first try to find the task in the expansion pack directory
281
+ const expansionPackPath = path.join(installDir, rootPath, "tasks", `${taskId}.md`);
282
+ if (await fileManager.pathExists(expansionPackPath)) {
283
+ taskPath = expansionPackPath;
284
+ } else {
285
+ // Fall back to core if not found in expansion pack
286
+ taskPath = await this.findTaskPath(taskId, installDir);
287
+ }
288
+ } else {
289
+ // For core, use the normal search
290
+ taskPath = await this.findTaskPath(taskId, installDir);
291
+ }
292
+
293
+ const commandPath = path.join(tasksDir, `${taskId}.md`);
294
+
295
+ if (taskPath) {
296
+ // Create command file with task content
297
+ let taskContent = await fileManager.readFile(taskPath);
298
+
299
+ // Replace {root} placeholder with the appropriate root path for this context
300
+ taskContent = taskContent.replace(/{root}/g, rootPath);
301
+
302
+ // Add command header
303
+ let commandContent = `# /${taskId} Task\n\n`;
304
+ commandContent += `When this command is used, execute the following task:\n\n`;
305
+ commandContent += taskContent;
306
+
307
+ await fileManager.writeFile(commandPath, commandContent);
308
+ console.log(chalk.green(`āœ“ Created task command: /${taskId}`));
309
+ }
310
+ }
311
+
312
+ console.log(chalk.green(`\nāœ“ Created Crush commands for ${packageName} in ${commandsBaseDir}`));
313
+ console.log(chalk.dim(` - Agents in: ${agentsDir}`));
314
+ console.log(chalk.dim(` - Tasks in: ${tasksDir}`));
315
+ }
316
+
203
317
  async setupWindsurf(installDir, selectedAgent) {
204
318
  const windsurfRulesDir = path.join(installDir, ".windsurf", "rules");
205
319
  const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -255,17 +369,17 @@ class IdeSetup extends BaseIdeSetup {
255
369
  async setupTrae(installDir, selectedAgent) {
256
370
  const traeRulesDir = path.join(installDir, ".trae", "rules");
257
371
  const agents = selectedAgent? [selectedAgent] : await this.getAllAgentIds(installDir);
258
-
372
+
259
373
  await fileManager.ensureDirectory(traeRulesDir);
260
-
374
+
261
375
  for (const agentId of agents) {
262
376
  // Find the agent file
263
377
  const agentPath = await this.findAgentPath(agentId, installDir);
264
-
378
+
265
379
  if (agentPath) {
266
380
  const agentContent = await fileManager.readFile(agentPath);
267
381
  const mdPath = path.join(traeRulesDir, `${agentId}.md`);
268
-
382
+
269
383
  // Create MD content (similar to Cursor but without frontmatter)
270
384
  let mdContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
271
385
  mdContent += `This rule is triggered when the user types \`@${agentId}\` and activates the ${await this.getAgentTitle(
@@ -294,7 +408,7 @@ class IdeSetup extends BaseIdeSetup {
294
408
  agentId,
295
409
  installDir
296
410
  )} persona and follow all instructions defined in the YAML configuration above.\n`;
297
-
411
+
298
412
  await fileManager.writeFile(mdPath, mdContent);
299
413
  console.log(chalk.green(`āœ“ Created rule: ${agentId}.md`));
300
414
  }
@@ -307,38 +421,38 @@ class IdeSetup extends BaseIdeSetup {
307
421
  path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
308
422
  path.join(installDir, "agents", `${agentId}.md`)
309
423
  ];
310
-
424
+
311
425
  // Also check expansion pack directories
312
426
  const glob = require("glob");
313
427
  const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
314
428
  for (const expDir of expansionDirs) {
315
429
  possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
316
430
  }
317
-
431
+
318
432
  for (const agentPath of possiblePaths) {
319
433
  if (await fileManager.pathExists(agentPath)) {
320
434
  return agentPath;
321
435
  }
322
436
  }
323
-
437
+
324
438
  return null;
325
439
  }
326
440
 
327
441
  async getAllAgentIds(installDir) {
328
442
  const glob = require("glob");
329
443
  const allAgentIds = [];
330
-
444
+
331
445
  // Check core agents in .bmad-core or root
332
446
  let agentsDir = path.join(installDir, ".bmad-core", "agents");
333
447
  if (!(await fileManager.pathExists(agentsDir))) {
334
448
  agentsDir = path.join(installDir, "agents");
335
449
  }
336
-
450
+
337
451
  if (await fileManager.pathExists(agentsDir)) {
338
452
  const agentFiles = glob.sync("*.md", { cwd: agentsDir });
339
453
  allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
340
454
  }
341
-
455
+
342
456
  // Also check for expansion pack agents in dot folders
343
457
  const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
344
458
  for (const expDir of expansionDirs) {
@@ -346,51 +460,51 @@ class IdeSetup extends BaseIdeSetup {
346
460
  const expAgentFiles = glob.sync("*.md", { cwd: fullExpDir });
347
461
  allAgentIds.push(...expAgentFiles.map((file) => path.basename(file, ".md")));
348
462
  }
349
-
463
+
350
464
  // Remove duplicates
351
465
  return [...new Set(allAgentIds)];
352
466
  }
353
467
 
354
468
  async getCoreAgentIds(installDir) {
355
469
  const allAgentIds = [];
356
-
470
+
357
471
  // Check core agents in .bmad-core or root only
358
472
  let agentsDir = path.join(installDir, ".bmad-core", "agents");
359
473
  if (!(await fileManager.pathExists(agentsDir))) {
360
474
  agentsDir = path.join(installDir, "bmad-core", "agents");
361
475
  }
362
-
476
+
363
477
  if (await fileManager.pathExists(agentsDir)) {
364
478
  const glob = require("glob");
365
479
  const agentFiles = glob.sync("*.md", { cwd: agentsDir });
366
480
  allAgentIds.push(...agentFiles.map((file) => path.basename(file, ".md")));
367
481
  }
368
-
482
+
369
483
  return [...new Set(allAgentIds)];
370
484
  }
371
485
 
372
486
  async getCoreTaskIds(installDir) {
373
487
  const allTaskIds = [];
374
-
488
+
375
489
  // Check core tasks in .bmad-core or root only
376
490
  let tasksDir = path.join(installDir, ".bmad-core", "tasks");
377
491
  if (!(await fileManager.pathExists(tasksDir))) {
378
492
  tasksDir = path.join(installDir, "bmad-core", "tasks");
379
493
  }
380
-
494
+
381
495
  if (await fileManager.pathExists(tasksDir)) {
382
496
  const glob = require("glob");
383
497
  const taskFiles = glob.sync("*.md", { cwd: tasksDir });
384
498
  allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
385
499
  }
386
-
500
+
387
501
  // Check common tasks
388
502
  const commonTasksDir = path.join(installDir, "common", "tasks");
389
503
  if (await fileManager.pathExists(commonTasksDir)) {
390
504
  const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
391
505
  allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
392
506
  }
393
-
507
+
394
508
  return [...new Set(allTaskIds)];
395
509
  }
396
510
 
@@ -400,20 +514,20 @@ class IdeSetup extends BaseIdeSetup {
400
514
  path.join(installDir, ".bmad-core", "agents", `${agentId}.md`),
401
515
  path.join(installDir, "agents", `${agentId}.md`)
402
516
  ];
403
-
517
+
404
518
  // Also check expansion pack directories
405
519
  const glob = require("glob");
406
520
  const expansionDirs = glob.sync(".*/agents", { cwd: installDir });
407
521
  for (const expDir of expansionDirs) {
408
522
  possiblePaths.push(path.join(installDir, expDir, `${agentId}.md`));
409
523
  }
410
-
524
+
411
525
  for (const agentPath of possiblePaths) {
412
526
  if (await fileManager.pathExists(agentPath)) {
413
527
  try {
414
528
  const agentContent = await fileManager.readFile(agentPath);
415
529
  const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
416
-
530
+
417
531
  if (yamlMatch) {
418
532
  const yaml = yamlMatch[1];
419
533
  const titleMatch = yaml.match(/title:\s*(.+)/);
@@ -426,9 +540,9 @@ class IdeSetup extends BaseIdeSetup {
426
540
  }
427
541
  }
428
542
  }
429
-
543
+
430
544
  // Fallback to formatted agent ID
431
- return agentId.split('-').map(word =>
545
+ return agentId.split('-').map(word =>
432
546
  word.charAt(0).toUpperCase() + word.slice(1)
433
547
  ).join(' ');
434
548
  }
@@ -436,25 +550,25 @@ class IdeSetup extends BaseIdeSetup {
436
550
  async getAllTaskIds(installDir) {
437
551
  const glob = require("glob");
438
552
  const allTaskIds = [];
439
-
553
+
440
554
  // Check core tasks in .bmad-core or root
441
555
  let tasksDir = path.join(installDir, ".bmad-core", "tasks");
442
556
  if (!(await fileManager.pathExists(tasksDir))) {
443
557
  tasksDir = path.join(installDir, "bmad-core", "tasks");
444
558
  }
445
-
559
+
446
560
  if (await fileManager.pathExists(tasksDir)) {
447
561
  const taskFiles = glob.sync("*.md", { cwd: tasksDir });
448
562
  allTaskIds.push(...taskFiles.map((file) => path.basename(file, ".md")));
449
563
  }
450
-
564
+
451
565
  // Check common tasks
452
566
  const commonTasksDir = path.join(installDir, "common", "tasks");
453
567
  if (await fileManager.pathExists(commonTasksDir)) {
454
568
  const commonTaskFiles = glob.sync("*.md", { cwd: commonTasksDir });
455
569
  allTaskIds.push(...commonTaskFiles.map((file) => path.basename(file, ".md")));
456
570
  }
457
-
571
+
458
572
  // Also check for expansion pack tasks in dot folders
459
573
  const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
460
574
  for (const expDir of expansionDirs) {
@@ -462,7 +576,7 @@ class IdeSetup extends BaseIdeSetup {
462
576
  const expTaskFiles = glob.sync("*.md", { cwd: fullExpDir });
463
577
  allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
464
578
  }
465
-
579
+
466
580
  // Check expansion-packs folder tasks
467
581
  const expansionPacksDir = path.join(installDir, "expansion-packs");
468
582
  if (await fileManager.pathExists(expansionPacksDir)) {
@@ -473,7 +587,7 @@ class IdeSetup extends BaseIdeSetup {
473
587
  allTaskIds.push(...expTaskFiles.map((file) => path.basename(file, ".md")));
474
588
  }
475
589
  }
476
-
590
+
477
591
  // Remove duplicates
478
592
  return [...new Set(allTaskIds)];
479
593
  }
@@ -485,16 +599,16 @@ class IdeSetup extends BaseIdeSetup {
485
599
  path.join(installDir, "bmad-core", "tasks", `${taskId}.md`),
486
600
  path.join(installDir, "common", "tasks", `${taskId}.md`)
487
601
  ];
488
-
602
+
489
603
  // Also check expansion pack directories
490
604
  const glob = require("glob");
491
-
605
+
492
606
  // Check dot folder expansion packs
493
607
  const expansionDirs = glob.sync(".*/tasks", { cwd: installDir });
494
608
  for (const expDir of expansionDirs) {
495
609
  possiblePaths.push(path.join(installDir, expDir, `${taskId}.md`));
496
610
  }
497
-
611
+
498
612
  // Check expansion-packs folder
499
613
  const expansionPacksDir = path.join(installDir, "expansion-packs");
500
614
  if (await fileManager.pathExists(expansionPacksDir)) {
@@ -503,13 +617,13 @@ class IdeSetup extends BaseIdeSetup {
503
617
  possiblePaths.push(path.join(expansionPacksDir, expDir, `${taskId}.md`));
504
618
  }
505
619
  }
506
-
620
+
507
621
  for (const taskPath of possiblePaths) {
508
622
  if (await fileManager.pathExists(taskPath)) {
509
623
  return taskPath;
510
624
  }
511
625
  }
512
-
626
+
513
627
  return null;
514
628
  }
515
629
 
@@ -526,7 +640,7 @@ class IdeSetup extends BaseIdeSetup {
526
640
  }
527
641
  return "BMad"; // fallback
528
642
  }
529
-
643
+
530
644
  const configContent = await fileManager.readFile(coreConfigPath);
531
645
  const config = yaml.load(configContent);
532
646
  return config.slashPrefix || "BMad";
@@ -538,11 +652,11 @@ class IdeSetup extends BaseIdeSetup {
538
652
 
539
653
  async getInstalledExpansionPacks(installDir) {
540
654
  const expansionPacks = [];
541
-
655
+
542
656
  // Check for dot-prefixed expansion packs in install directory
543
657
  const glob = require("glob");
544
658
  const dotExpansions = glob.sync(".bmad-*", { cwd: installDir });
545
-
659
+
546
660
  for (const dotExpansion of dotExpansions) {
547
661
  if (dotExpansion !== ".bmad-core") {
548
662
  const packPath = path.join(installDir, dotExpansion);
@@ -553,15 +667,15 @@ class IdeSetup extends BaseIdeSetup {
553
667
  });
554
668
  }
555
669
  }
556
-
670
+
557
671
  // Check for expansion-packs directory style
558
672
  const expansionPacksDir = path.join(installDir, "expansion-packs");
559
673
  if (await fileManager.pathExists(expansionPacksDir)) {
560
674
  const packDirs = glob.sync("*", { cwd: expansionPacksDir });
561
-
675
+
562
676
  for (const packDir of packDirs) {
563
677
  const packPath = path.join(expansionPacksDir, packDir);
564
- if ((await fileManager.pathExists(packPath)) &&
678
+ if ((await fileManager.pathExists(packPath)) &&
565
679
  (await fileManager.pathExists(path.join(packPath, "config.yaml")))) {
566
680
  expansionPacks.push({
567
681
  name: packDir,
@@ -570,7 +684,7 @@ class IdeSetup extends BaseIdeSetup {
570
684
  }
571
685
  }
572
686
  }
573
-
687
+
574
688
  return expansionPacks;
575
689
  }
576
690
 
@@ -585,7 +699,7 @@ class IdeSetup extends BaseIdeSetup {
585
699
  } catch (error) {
586
700
  console.warn(`Failed to read expansion pack slashPrefix from ${packPath}: ${error.message}`);
587
701
  }
588
-
702
+
589
703
  return path.basename(packPath); // fallback to directory name
590
704
  }
591
705
 
@@ -594,7 +708,7 @@ class IdeSetup extends BaseIdeSetup {
594
708
  if (!(await fileManager.pathExists(agentsDir))) {
595
709
  return [];
596
710
  }
597
-
711
+
598
712
  try {
599
713
  const glob = require("glob");
600
714
  const agentFiles = glob.sync("*.md", { cwd: agentsDir });
@@ -610,7 +724,7 @@ class IdeSetup extends BaseIdeSetup {
610
724
  if (!(await fileManager.pathExists(tasksDir))) {
611
725
  return [];
612
726
  }
613
-
727
+
614
728
  try {
615
729
  const glob = require("glob");
616
730
  const taskFiles = glob.sync("*.md", { cwd: tasksDir });
@@ -688,7 +802,7 @@ class IdeSetup extends BaseIdeSetup {
688
802
  newModesContent += ` - slug: ${slug}\n`;
689
803
  newModesContent += ` name: '${icon} ${title}'\n`;
690
804
  if (permissions) {
691
- newModesContent += ` description: '${permissions.description}'\n`;
805
+ newModesContent += ` description: '${permissions.description}'\n`;
692
806
  }
693
807
  newModesContent += ` roleDefinition: ${roleDefinition}\n`;
694
808
  newModesContent += ` whenToUse: ${whenToUse}\n`;
@@ -730,7 +844,7 @@ class IdeSetup extends BaseIdeSetup {
730
844
 
731
845
  return true;
732
846
  }
733
-
847
+
734
848
  async setupKilocode(installDir, selectedAgent) {
735
849
  const filePath = path.join(installDir, ".kilocodemodes");
736
850
  const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -788,7 +902,7 @@ class IdeSetup extends BaseIdeSetup {
788
902
  newContent += ` - slug: ${slug}\n`;
789
903
  newContent += ` name: '${icon} ${title}'\n`;
790
904
  if (agentPermission) {
791
- newContent += ` description: '${agentPermission.description}'\n`;
905
+ newContent += ` description: '${agentPermission.description}'\n`;
792
906
  }
793
907
 
794
908
  newContent += ` roleDefinition: ${roleDefinition}\n`;
@@ -821,7 +935,7 @@ class IdeSetup extends BaseIdeSetup {
821
935
 
822
936
  return true;
823
937
  }
824
-
938
+
825
939
  async setupCline(installDir, selectedAgent) {
826
940
  const clineRulesDir = path.join(installDir, ".clinerules");
827
941
  const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
@@ -891,7 +1005,7 @@ class IdeSetup extends BaseIdeSetup {
891
1005
  const settingsContent = await fileManager.readFile(settingsPath);
892
1006
  const settings = JSON.parse(settingsContent);
893
1007
  let updated = false;
894
-
1008
+
895
1009
  // Handle contextFileName property
896
1010
  if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
897
1011
  const originalLength = settings.contextFileName.length;
@@ -902,7 +1016,7 @@ class IdeSetup extends BaseIdeSetup {
902
1016
  updated = true;
903
1017
  }
904
1018
  }
905
-
1019
+
906
1020
  if (updated) {
907
1021
  await fileManager.writeFile(
908
1022
  settingsPath,
@@ -935,7 +1049,7 @@ class IdeSetup extends BaseIdeSetup {
935
1049
 
936
1050
  if (agentPath) {
937
1051
  const agentContent = await fileManager.readFile(agentPath);
938
-
1052
+
939
1053
  // Create properly formatted agent rule content (similar to trae)
940
1054
  let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
941
1055
  agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
@@ -964,7 +1078,7 @@ class IdeSetup extends BaseIdeSetup {
964
1078
  agentId,
965
1079
  installDir
966
1080
  )} persona and follow all instructions defined in the YAML configuration above.\n`;
967
-
1081
+
968
1082
  // Add to concatenated content with separator
969
1083
  concatenatedContent += agentRuleContent + "\n\n---\n\n";
970
1084
  console.log(chalk.green(`āœ“ Added context for @${agentId}`));
@@ -991,7 +1105,7 @@ class IdeSetup extends BaseIdeSetup {
991
1105
  const settingsContent = await fileManager.readFile(settingsPath);
992
1106
  const settings = JSON.parse(settingsContent);
993
1107
  let updated = false;
994
-
1108
+
995
1109
  // Handle contextFileName property
996
1110
  if (settings.contextFileName && Array.isArray(settings.contextFileName)) {
997
1111
  const originalLength = settings.contextFileName.length;
@@ -1002,7 +1116,7 @@ class IdeSetup extends BaseIdeSetup {
1002
1116
  updated = true;
1003
1117
  }
1004
1118
  }
1005
-
1119
+
1006
1120
  if (updated) {
1007
1121
  await fileManager.writeFile(
1008
1122
  settingsPath,
@@ -1035,7 +1149,7 @@ class IdeSetup extends BaseIdeSetup {
1035
1149
 
1036
1150
  if (agentPath) {
1037
1151
  const agentContent = await fileManager.readFile(agentPath);
1038
-
1152
+
1039
1153
  // Create properly formatted agent rule content (similar to gemini)
1040
1154
  let agentRuleContent = `# ${agentId.toUpperCase()} Agent Rule\n\n`;
1041
1155
  agentRuleContent += `This rule is triggered when the user types \`*${agentId}\` and activates the ${await this.getAgentTitle(
@@ -1064,7 +1178,7 @@ class IdeSetup extends BaseIdeSetup {
1064
1178
  agentId,
1065
1179
  installDir
1066
1180
  )} persona and follow all instructions defined in the YAML configuration above.\n`;
1067
-
1181
+
1068
1182
  // Add to concatenated content with separator
1069
1183
  concatenatedContent += agentRuleContent + "\n\n---\n\n";
1070
1184
  console.log(chalk.green(`āœ“ Added context for *${agentId}`));
@@ -1082,10 +1196,10 @@ class IdeSetup extends BaseIdeSetup {
1082
1196
  async setupGitHubCopilot(installDir, selectedAgent, spinner = null, preConfiguredSettings = null) {
1083
1197
  // Configure VS Code workspace settings first to avoid UI conflicts with loading spinners
1084
1198
  await this.configureVsCodeSettings(installDir, spinner, preConfiguredSettings);
1085
-
1199
+
1086
1200
  const chatmodesDir = path.join(installDir, ".github", "chatmodes");
1087
1201
  const agents = selectedAgent ? [selectedAgent] : await this.getAllAgentIds(installDir);
1088
-
1202
+
1089
1203
  await fileManager.ensureDirectory(chatmodesDir);
1090
1204
 
1091
1205
  for (const agentId of agents) {
@@ -1097,7 +1211,7 @@ class IdeSetup extends BaseIdeSetup {
1097
1211
  // Create chat mode file with agent content
1098
1212
  const agentContent = await fileManager.readFile(agentPath);
1099
1213
  const agentTitle = await this.getAgentTitle(agentId, installDir);
1100
-
1214
+
1101
1215
  // Extract whenToUse for the description
1102
1216
  const yamlMatch = agentContent.match(/```ya?ml\r?\n([\s\S]*?)```/);
1103
1217
  let description = `Activates the ${agentTitle} agent persona.`;
@@ -1107,7 +1221,7 @@ class IdeSetup extends BaseIdeSetup {
1107
1221
  description = whenToUseMatch[1];
1108
1222
  }
1109
1223
  }
1110
-
1224
+
1111
1225
  let chatmodeContent = `---
1112
1226
  description: "${description.replace(/"/g, '\\"')}"
1113
1227
  tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems', 'usages', 'editFiles', 'runCommands', 'runTasks', 'runTests', 'search', 'searchResults', 'terminalLastCommand', 'terminalSelection', 'testFailure']
@@ -1130,9 +1244,9 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1130
1244
  async configureVsCodeSettings(installDir, spinner, preConfiguredSettings = null) {
1131
1245
  const vscodeDir = path.join(installDir, ".vscode");
1132
1246
  const settingsPath = path.join(vscodeDir, "settings.json");
1133
-
1247
+
1134
1248
  await fileManager.ensureDirectory(vscodeDir);
1135
-
1249
+
1136
1250
  // Read existing settings if they exist
1137
1251
  let existingSettings = {};
1138
1252
  if (await fileManager.pathExists(settingsPath)) {
@@ -1145,7 +1259,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1145
1259
  existingSettings = {};
1146
1260
  }
1147
1261
  }
1148
-
1262
+
1149
1263
  // Use pre-configured settings if provided, otherwise prompt
1150
1264
  let configChoice;
1151
1265
  if (preConfiguredSettings && preConfiguredSettings.configChoice) {
@@ -1157,7 +1271,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1157
1271
  console.log(chalk.blue("šŸ”§ Github Copilot Agent Settings Configuration"));
1158
1272
  console.log(chalk.dim("BMad works best with specific VS Code settings for optimal agent experience."));
1159
1273
  console.log(''); // Add extra spacing
1160
-
1274
+
1161
1275
  const response = await inquirer.prompt([
1162
1276
  {
1163
1277
  type: 'list',
@@ -1182,9 +1296,9 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1182
1296
  ]);
1183
1297
  configChoice = response.configChoice;
1184
1298
  }
1185
-
1299
+
1186
1300
  let bmadSettings = {};
1187
-
1301
+
1188
1302
  if (configChoice === 'skip') {
1189
1303
  console.log(chalk.yellow("āš ļø Skipping VS Code settings configuration."));
1190
1304
  console.log(chalk.dim("You can manually configure these settings in .vscode/settings.json:"));
@@ -1196,7 +1310,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1196
1310
  console.log(chalk.dim(" • chat.tools.autoApprove: false"));
1197
1311
  return true;
1198
1312
  }
1199
-
1313
+
1200
1314
  if (configChoice === 'defaults') {
1201
1315
  // Use recommended defaults
1202
1316
  bmadSettings = {
@@ -1211,14 +1325,14 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1211
1325
  } else {
1212
1326
  // Manual configuration
1213
1327
  console.log(chalk.blue("\nšŸ“‹ Let's configure each setting for your preferences:"));
1214
-
1328
+
1215
1329
  // Pause spinner during manual configuration prompts
1216
1330
  let spinnerWasActive = false;
1217
1331
  if (spinner && spinner.isSpinning) {
1218
1332
  spinner.stop();
1219
1333
  spinnerWasActive = true;
1220
1334
  }
1221
-
1335
+
1222
1336
  const manualSettings = await inquirer.prompt([
1223
1337
  {
1224
1338
  type: 'input',
@@ -1263,7 +1377,7 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1263
1377
  if (spinner && spinnerWasActive) {
1264
1378
  spinner.start();
1265
1379
  }
1266
-
1380
+
1267
1381
  bmadSettings = {
1268
1382
  "chat.agent.enabled": true, // Always enabled - required for BMad agents
1269
1383
  "chat.agent.maxRequests": parseInt(manualSettings.maxRequests),
@@ -1272,16 +1386,16 @@ tools: ['changes', 'codebase', 'fetch', 'findTestFiles', 'githubRepo', 'problems
1272
1386
  "github.copilot.chat.agent.autoFix": manualSettings.autoFix,
1273
1387
  "chat.tools.autoApprove": manualSettings.autoApprove
1274
1388
  };
1275
-
1389
+
1276
1390
  console.log(chalk.green("āœ“ Custom settings configured"));
1277
1391
  }
1278
-
1392
+
1279
1393
  // Merge settings (existing settings take precedence to avoid overriding user preferences)
1280
1394
  const mergedSettings = { ...bmadSettings, ...existingSettings };
1281
-
1395
+
1282
1396
  // Write the updated settings
1283
1397
  await fileManager.writeFile(settingsPath, JSON.stringify(mergedSettings, null, 2));
1284
-
1398
+
1285
1399
  console.log(chalk.green("āœ“ VS Code workspace settings configured successfully"));
1286
1400
  console.log(chalk.dim(" Settings written to .vscode/settings.json:"));
1287
1401
  Object.entries(bmadSettings).forEach(([key, value]) => {