bmad-method 4.37.0 → 5.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.github/workflows/promote-to-stable.yml +144 -0
  2. package/CHANGELOG.md +4 -14
  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/flattener/main.js +474 -15
  42. package/tools/flattener/projectRoot.js +182 -23
  43. package/tools/flattener/stats.helpers.js +331 -0
  44. package/tools/flattener/stats.js +64 -14
  45. package/tools/flattener/test-matrix.js +405 -0
  46. package/tools/installer/bin/bmad.js +33 -32
  47. package/tools/installer/config/install.config.yaml +11 -1
  48. package/tools/installer/lib/file-manager.js +1 -1
  49. package/tools/installer/lib/ide-base-setup.js +1 -1
  50. package/tools/installer/lib/ide-setup.js +197 -83
  51. package/tools/installer/lib/installer.js +3 -3
  52. package/tools/installer/package.json +1 -1
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+ /* deno-lint-ignore-file */
3
+ /*
4
+ Automatic test matrix for project root detection.
5
+ Creates temporary fixtures for various ecosystems and validates findProjectRoot().
6
+ No external options or flags required. Safe to run multiple times.
7
+ */
8
+
9
+ const os = require("node:os");
10
+ const path = require("node:path");
11
+ const fs = require("fs-extra");
12
+ const { promisify } = require("node:util");
13
+ const { execFile } = require("node:child_process");
14
+ const process = require("node:process");
15
+ const execFileAsync = promisify(execFile);
16
+
17
+ const { findProjectRoot } = require("./projectRoot.js");
18
+
19
+ async function cmdAvailable(cmd) {
20
+ try {
21
+ await execFileAsync(cmd, ["--version"], { timeout: 500, windowsHide: true });
22
+ return true;
23
+ } catch {
24
+ return false;
25
+ }
26
+
27
+ async function testSvnMarker() {
28
+ const root = await mkTmpDir("svn");
29
+ const nested = path.join(root, "proj", "code");
30
+ await fs.ensureDir(nested);
31
+ await fs.ensureDir(path.join(root, ".svn"));
32
+ const found = await findProjectRoot(nested);
33
+ assertEqual(found, root, ".svn marker should be detected");
34
+ return { name: "svn-marker", ok: true };
35
+ }
36
+
37
+ async function testSymlinkStart() {
38
+ const root = await mkTmpDir("symlink-start");
39
+ const nested = path.join(root, "a", "b");
40
+ await fs.ensureDir(nested);
41
+ await fs.writeFile(path.join(root, ".project-root"), "\n");
42
+ const tmp = await mkTmpDir("symlink-tmp");
43
+ const link = path.join(tmp, "link-to-b");
44
+ try {
45
+ await fs.symlink(nested, link);
46
+ } catch {
47
+ // symlink may not be permitted on some systems; skip
48
+ return { name: "symlink-start", ok: true, skipped: true };
49
+ }
50
+ const found = await findProjectRoot(link);
51
+ assertEqual(found, root, "should resolve symlinked start to real root");
52
+ return { name: "symlink-start", ok: true };
53
+ }
54
+
55
+ async function testSubmoduleLikeInnerGitFile() {
56
+ const root = await mkTmpDir("submodule-like");
57
+ const mid = path.join(root, "mid");
58
+ const leaf = path.join(mid, "leaf");
59
+ await fs.ensureDir(leaf);
60
+ // outer repo
61
+ await fs.ensureDir(path.join(root, ".git"));
62
+ // inner submodule-like .git file
63
+ await fs.writeFile(path.join(mid, ".git"), "gitdir: ../.git/modules/mid\n");
64
+ const found = await findProjectRoot(leaf);
65
+ assertEqual(found, root, "outermost .git should win on tie weight");
66
+ return { name: "submodule-like-gitfile", ok: true };
67
+ }
68
+ }
69
+
70
+ async function mkTmpDir(name) {
71
+ const base = await fs.realpath(os.tmpdir());
72
+ const dir = await fs.mkdtemp(path.join(base, `flattener-${name}-`));
73
+ return dir;
74
+ }
75
+
76
+ function assertEqual(actual, expected, msg) {
77
+ if (actual !== expected) {
78
+ throw new Error(`${msg}: expected=\"${expected}\" actual=\"${actual}\"`);
79
+ }
80
+ }
81
+
82
+ async function testSentinel() {
83
+ const root = await mkTmpDir("sentinel");
84
+ const nested = path.join(root, "a", "b", "c");
85
+ await fs.ensureDir(nested);
86
+ await fs.writeFile(path.join(root, ".project-root"), "\n");
87
+ const found = await findProjectRoot(nested);
88
+ await assertEqual(found, root, "sentinel .project-root should win");
89
+ return { name: "sentinel", ok: true };
90
+ }
91
+
92
+ async function testOtherSentinels() {
93
+ const root = await mkTmpDir("other-sentinels");
94
+ const nested = path.join(root, "x", "y");
95
+ await fs.ensureDir(nested);
96
+ await fs.writeFile(path.join(root, ".workspace-root"), "\n");
97
+ const found1 = await findProjectRoot(nested);
98
+ assertEqual(found1, root, "sentinel .workspace-root should win");
99
+
100
+ await fs.remove(path.join(root, ".workspace-root"));
101
+ await fs.writeFile(path.join(root, ".repo-root"), "\n");
102
+ const found2 = await findProjectRoot(nested);
103
+ assertEqual(found2, root, "sentinel .repo-root should win");
104
+ return { name: "other-sentinels", ok: true };
105
+ }
106
+
107
+ async function testGitCliAndMarker() {
108
+ const hasGit = await cmdAvailable("git");
109
+ if (!hasGit) return { name: "git-cli", ok: true, skipped: true };
110
+
111
+ const root = await mkTmpDir("git");
112
+ const nested = path.join(root, "pkg", "src");
113
+ await fs.ensureDir(nested);
114
+ await execFileAsync("git", ["init"], { cwd: root, timeout: 2000 });
115
+ const found = await findProjectRoot(nested);
116
+ await assertEqual(found, root, "git toplevel should be detected");
117
+ return { name: "git-cli", ok: true };
118
+ }
119
+
120
+ async function testHgMarkerOrCli() {
121
+ // Prefer simple marker test to avoid requiring Mercurial install
122
+ const root = await mkTmpDir("hg");
123
+ const nested = path.join(root, "lib");
124
+ await fs.ensureDir(nested);
125
+ await fs.ensureDir(path.join(root, ".hg"));
126
+ const found = await findProjectRoot(nested);
127
+ await assertEqual(found, root, ".hg marker should be detected");
128
+ return { name: "hg-marker", ok: true };
129
+ }
130
+
131
+ async function testWorkspacePnpm() {
132
+ const root = await mkTmpDir("pnpm-workspace");
133
+ const pkgA = path.join(root, "packages", "a");
134
+ await fs.ensureDir(pkgA);
135
+ await fs.writeFile(path.join(root, "pnpm-workspace.yaml"), "packages:\n - packages/*\n");
136
+ const found = await findProjectRoot(pkgA);
137
+ await assertEqual(found, root, "pnpm-workspace.yaml should be detected");
138
+ return { name: "pnpm-workspace", ok: true };
139
+ }
140
+
141
+ async function testPackageJsonWorkspaces() {
142
+ const root = await mkTmpDir("package-workspaces");
143
+ const pkgA = path.join(root, "packages", "a");
144
+ await fs.ensureDir(pkgA);
145
+ await fs.writeJson(path.join(root, "package.json"), { private: true, workspaces: ["packages/*"] }, { spaces: 2 });
146
+ const found = await findProjectRoot(pkgA);
147
+ await assertEqual(found, root, "package.json workspaces should be detected");
148
+ return { name: "package.json-workspaces", ok: true };
149
+ }
150
+
151
+ async function testLockfiles() {
152
+ const root = await mkTmpDir("lockfiles");
153
+ const nested = path.join(root, "src");
154
+ await fs.ensureDir(nested);
155
+ await fs.writeFile(path.join(root, "yarn.lock"), "\n");
156
+ const found = await findProjectRoot(nested);
157
+ await assertEqual(found, root, "yarn.lock should be detected");
158
+ return { name: "lockfiles", ok: true };
159
+ }
160
+
161
+ async function testLanguageConfigs() {
162
+ const root = await mkTmpDir("lang-configs");
163
+ const nested = path.join(root, "x", "y");
164
+ await fs.ensureDir(nested);
165
+ await fs.writeFile(path.join(root, "pyproject.toml"), "[tool.poetry]\nname='tmp'\n");
166
+ const found = await findProjectRoot(nested);
167
+ await assertEqual(found, root, "pyproject.toml should be detected");
168
+ return { name: "language-configs", ok: true };
169
+ }
170
+
171
+ async function testPreferOuterOnTie() {
172
+ const root = await mkTmpDir("tie");
173
+ const mid = path.join(root, "mid");
174
+ const leaf = path.join(mid, "leaf");
175
+ await fs.ensureDir(leaf);
176
+ // same weight marker at two levels
177
+ await fs.writeFile(path.join(root, "requirements.txt"), "\n");
178
+ await fs.writeFile(path.join(mid, "requirements.txt"), "\n");
179
+ const found = await findProjectRoot(leaf);
180
+ await assertEqual(found, root, "outermost directory should win on equal weight");
181
+ return { name: "prefer-outermost-tie", ok: true };
182
+ }
183
+
184
+ // Additional coverage: Bazel, Nx/Turbo/Rush, Go workspaces, Deno, Java/Scala, PHP, Rust, Nix, Changesets, env markers,
185
+ // and priority interaction between package.json and lockfiles.
186
+
187
+ async function testBazelWorkspace() {
188
+ const root = await mkTmpDir("bazel");
189
+ const nested = path.join(root, "apps", "svc");
190
+ await fs.ensureDir(nested);
191
+ await fs.writeFile(path.join(root, "WORKSPACE"), "workspace(name=\"tmp\")\n");
192
+ const found = await findProjectRoot(nested);
193
+ await assertEqual(found, root, "Bazel WORKSPACE should be detected");
194
+ return { name: "bazel-workspace", ok: true };
195
+ }
196
+
197
+ async function testNx() {
198
+ const root = await mkTmpDir("nx");
199
+ const nested = path.join(root, "apps", "web");
200
+ await fs.ensureDir(nested);
201
+ await fs.writeJson(path.join(root, "nx.json"), { npmScope: "tmp" }, { spaces: 2 });
202
+ const found = await findProjectRoot(nested);
203
+ await assertEqual(found, root, "nx.json should be detected");
204
+ return { name: "nx", ok: true };
205
+ }
206
+
207
+ async function testTurbo() {
208
+ const root = await mkTmpDir("turbo");
209
+ const nested = path.join(root, "packages", "x");
210
+ await fs.ensureDir(nested);
211
+ await fs.writeJson(path.join(root, "turbo.json"), { pipeline: {} }, { spaces: 2 });
212
+ const found = await findProjectRoot(nested);
213
+ await assertEqual(found, root, "turbo.json should be detected");
214
+ return { name: "turbo", ok: true };
215
+ }
216
+
217
+ async function testRush() {
218
+ const root = await mkTmpDir("rush");
219
+ const nested = path.join(root, "apps", "a");
220
+ await fs.ensureDir(nested);
221
+ await fs.writeJson(path.join(root, "rush.json"), { projectFolderMinDepth: 1 }, { spaces: 2 });
222
+ const found = await findProjectRoot(nested);
223
+ await assertEqual(found, root, "rush.json should be detected");
224
+ return { name: "rush", ok: true };
225
+ }
226
+
227
+ async function testGoWorkAndMod() {
228
+ const root = await mkTmpDir("gowork");
229
+ const mod = path.join(root, "modA");
230
+ const nested = path.join(mod, "pkg");
231
+ await fs.ensureDir(nested);
232
+ await fs.writeFile(path.join(root, "go.work"), "go 1.22\nuse ./modA\n");
233
+ await fs.writeFile(path.join(mod, "go.mod"), "module example.com/a\ngo 1.22\n");
234
+ const found = await findProjectRoot(nested);
235
+ await assertEqual(found, root, "go.work should define the workspace root");
236
+ return { name: "go-work", ok: true };
237
+ }
238
+
239
+ async function testDenoJson() {
240
+ const root = await mkTmpDir("deno");
241
+ const nested = path.join(root, "src");
242
+ await fs.ensureDir(nested);
243
+ await fs.writeJson(path.join(root, "deno.json"), { tasks: {} }, { spaces: 2 });
244
+ const found = await findProjectRoot(nested);
245
+ await assertEqual(found, root, "deno.json should be detected");
246
+ return { name: "deno-json", ok: true };
247
+ }
248
+
249
+ async function testGradleSettings() {
250
+ const root = await mkTmpDir("gradle");
251
+ const nested = path.join(root, "app");
252
+ await fs.ensureDir(nested);
253
+ await fs.writeFile(path.join(root, "settings.gradle"), "rootProject.name='tmp'\n");
254
+ const found = await findProjectRoot(nested);
255
+ await assertEqual(found, root, "settings.gradle should be detected");
256
+ return { name: "gradle-settings", ok: true };
257
+ }
258
+
259
+ async function testMavenPom() {
260
+ const root = await mkTmpDir("maven");
261
+ const nested = path.join(root, "module");
262
+ await fs.ensureDir(nested);
263
+ await fs.writeFile(path.join(root, "pom.xml"), "<project></project>\n");
264
+ const found = await findProjectRoot(nested);
265
+ await assertEqual(found, root, "pom.xml should be detected");
266
+ return { name: "maven-pom", ok: true };
267
+ }
268
+
269
+ async function testSbtBuild() {
270
+ const root = await mkTmpDir("sbt");
271
+ const nested = path.join(root, "sub");
272
+ await fs.ensureDir(nested);
273
+ await fs.writeFile(path.join(root, "build.sbt"), "name := \"tmp\"\n");
274
+ const found = await findProjectRoot(nested);
275
+ await assertEqual(found, root, "build.sbt should be detected");
276
+ return { name: "sbt-build", ok: true };
277
+ }
278
+
279
+ async function testComposer() {
280
+ const root = await mkTmpDir("composer");
281
+ const nested = path.join(root, "src");
282
+ await fs.ensureDir(nested);
283
+ await fs.writeJson(path.join(root, "composer.json"), { name: "tmp/pkg" }, { spaces: 2 });
284
+ await fs.writeFile(path.join(root, "composer.lock"), "{}\n");
285
+ const found = await findProjectRoot(nested);
286
+ await assertEqual(found, root, "composer.{json,lock} should be detected");
287
+ return { name: "composer", ok: true };
288
+ }
289
+
290
+ async function testCargo() {
291
+ const root = await mkTmpDir("cargo");
292
+ const nested = path.join(root, "src");
293
+ await fs.ensureDir(nested);
294
+ await fs.writeFile(path.join(root, "Cargo.toml"), "[package]\nname='tmp'\nversion='0.0.0'\n");
295
+ const found = await findProjectRoot(nested);
296
+ await assertEqual(found, root, "Cargo.toml should be detected");
297
+ return { name: "cargo", ok: true };
298
+ }
299
+
300
+ async function testNixFlake() {
301
+ const root = await mkTmpDir("nix");
302
+ const nested = path.join(root, "work");
303
+ await fs.ensureDir(nested);
304
+ await fs.writeFile(path.join(root, "flake.nix"), "{ }\n");
305
+ const found = await findProjectRoot(nested);
306
+ await assertEqual(found, root, "flake.nix should be detected");
307
+ return { name: "nix-flake", ok: true };
308
+ }
309
+
310
+ async function testChangesetConfig() {
311
+ const root = await mkTmpDir("changeset");
312
+ const nested = path.join(root, "pkg");
313
+ await fs.ensureDir(nested);
314
+ await fs.ensureDir(path.join(root, ".changeset"));
315
+ await fs.writeJson(path.join(root, ".changeset", "config.json"), { $schema: "https://unpkg.com/@changesets/config@2.3.1/schema.json" }, { spaces: 2 });
316
+ const found = await findProjectRoot(nested);
317
+ await assertEqual(found, root, ".changeset/config.json should be detected");
318
+ return { name: "changesets", ok: true };
319
+ }
320
+
321
+ async function testEnvCustomMarker() {
322
+ const root = await mkTmpDir("env-marker");
323
+ const nested = path.join(root, "dir");
324
+ await fs.ensureDir(nested);
325
+ await fs.writeFile(path.join(root, "MY_ROOT"), "\n");
326
+ const prev = process.env.PROJECT_ROOT_MARKERS;
327
+ process.env.PROJECT_ROOT_MARKERS = "MY_ROOT";
328
+ try {
329
+ const found = await findProjectRoot(nested);
330
+ await assertEqual(found, root, "custom env marker should be honored");
331
+ } finally {
332
+ if (prev === undefined) delete process.env.PROJECT_ROOT_MARKERS; else process.env.PROJECT_ROOT_MARKERS = prev;
333
+ }
334
+ return { name: "env-custom-marker", ok: true };
335
+ }
336
+
337
+ async function testPackageLowPriorityVsLock() {
338
+ const root = await mkTmpDir("pkg-vs-lock");
339
+ const nested = path.join(root, "nested");
340
+ await fs.ensureDir(path.join(nested, "deep"));
341
+ await fs.writeJson(path.join(nested, "package.json"), { name: "nested" }, { spaces: 2 });
342
+ await fs.writeFile(path.join(root, "yarn.lock"), "\n");
343
+ const found = await findProjectRoot(path.join(nested, "deep"));
344
+ await assertEqual(found, root, "lockfile at root should outrank nested package.json");
345
+ return { name: "package-vs-lock-priority", ok: true };
346
+ }
347
+
348
+ async function run() {
349
+ const tests = [
350
+ testSentinel,
351
+ testOtherSentinels,
352
+ testGitCliAndMarker,
353
+ testHgMarkerOrCli,
354
+ testWorkspacePnpm,
355
+ testPackageJsonWorkspaces,
356
+ testLockfiles,
357
+ testLanguageConfigs,
358
+ testPreferOuterOnTie,
359
+ testBazelWorkspace,
360
+ testNx,
361
+ testTurbo,
362
+ testRush,
363
+ testGoWorkAndMod,
364
+ testDenoJson,
365
+ testGradleSettings,
366
+ testMavenPom,
367
+ testSbtBuild,
368
+ testComposer,
369
+ testCargo,
370
+ testNixFlake,
371
+ testChangesetConfig,
372
+ testEnvCustomMarker,
373
+ testPackageLowPriorityVsLock,
374
+ testSvnMarker,
375
+ testSymlinkStart,
376
+ testSubmoduleLikeInnerGitFile,
377
+ ];
378
+
379
+ const results = [];
380
+ for (const t of tests) {
381
+ try {
382
+ const r = await t();
383
+ results.push({ ...r, ok: true });
384
+ console.log(`✔ ${r.name}${r.skipped ? " (skipped)" : ""}`);
385
+ } catch (err) {
386
+ console.error(`✖ ${t.name}:`, err && err.message ? err.message : err);
387
+ results.push({ name: t.name, ok: false, error: String(err) });
388
+ }
389
+ }
390
+
391
+ const failed = results.filter((r) => !r.ok);
392
+ console.log("\nSummary:");
393
+ for (const r of results) {
394
+ console.log(`- ${r.name}: ${r.ok ? "ok" : "FAIL"}${r.skipped ? " (skipped)" : ""}`);
395
+ }
396
+
397
+ if (failed.length) {
398
+ process.exitCode = 1;
399
+ }
400
+ }
401
+
402
+ run().catch((e) => {
403
+ console.error("Fatal error:", e);
404
+ process.exit(1);
405
+ });
@@ -4,8 +4,8 @@ const { program } = require('commander');
4
4
  const path = require('path');
5
5
  const fs = require('fs').promises;
6
6
  const yaml = require('js-yaml');
7
- const chalk = require('chalk');
8
- const inquirer = require('inquirer');
7
+ const chalk = require('chalk').default || require('chalk');
8
+ const inquirer = require('inquirer').default || require('inquirer');
9
9
  const semver = require('semver');
10
10
  const https = require('https');
11
11
 
@@ -45,7 +45,7 @@ program
45
45
  .option('-f, --full', 'Install complete BMad Method')
46
46
  .option('-x, --expansion-only', 'Install only expansion packs (no bmad-core)')
47
47
  .option('-d, --directory <path>', 'Installation directory')
48
- .option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, other)')
48
+ .option('-i, --ide <ide...>', 'Configure for specific IDE(s) - can specify multiple (cursor, claude-code, windsurf, trae, roo, kilo, cline, gemini, qwen-code, github-copilot, crush, other)')
49
49
  .option('-e, --expansion-packs <packs...>', 'Install specific expansion packs (can specify multiple)')
50
50
  .action(async (options) => {
51
51
  try {
@@ -183,17 +183,17 @@ program
183
183
  });
184
184
 
185
185
  async function promptInstallation() {
186
-
186
+
187
187
  // Display ASCII logo
188
188
  console.log(chalk.bold.cyan(`
189
- ██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗
189
+ ██████╗ ███╗ ███╗ █████╗ ██████╗ ███╗ ███╗███████╗████████╗██╗ ██╗ ██████╗ ██████╗
190
190
  ██╔══██╗████╗ ████║██╔══██╗██╔══██╗ ████╗ ████║██╔════╝╚══██╔══╝██║ ██║██╔═══██╗██╔══██╗
191
191
  ██████╔╝██╔████╔██║███████║██║ ██║█████╗██╔████╔██║█████╗ ██║ ███████║██║ ██║██║ ██║
192
192
  ██╔══██╗██║╚██╔╝██║██╔══██║██║ ██║╚════╝██║╚██╔╝██║██╔══╝ ██║ ██╔══██║██║ ██║██║ ██║
193
193
  ██████╔╝██║ ╚═╝ ██║██║ ██║██████╔╝ ██║ ╚═╝ ██║███████╗ ██║ ██║ ██║╚██████╔╝██████╔╝
194
- ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝
194
+ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝
195
195
  `));
196
-
196
+
197
197
  console.log(chalk.bold.magenta('🚀 Universal AI Agent Framework for Any Domain'));
198
198
  console.log(chalk.bold.blue(`✨ Installer v${version}\n`));
199
199
 
@@ -218,63 +218,63 @@ async function promptInstallation() {
218
218
  // Detect existing installations
219
219
  const installDir = path.resolve(directory);
220
220
  const state = await installer.detectInstallationState(installDir);
221
-
221
+
222
222
  // Check for existing expansion packs
223
223
  const existingExpansionPacks = state.expansionPacks || {};
224
-
224
+
225
225
  // Get available expansion packs
226
226
  const availableExpansionPacks = await installer.getAvailableExpansionPacks();
227
-
227
+
228
228
  // Build choices list
229
229
  const choices = [];
230
-
230
+
231
231
  // Load core config to get short-title
232
232
  const coreConfigPath = path.join(__dirname, '..', '..', '..', 'bmad-core', 'core-config.yaml');
233
233
  const coreConfig = yaml.load(await fs.readFile(coreConfigPath, 'utf8'));
234
234
  const coreShortTitle = coreConfig['short-title'] || 'BMad Agile Core System';
235
-
235
+
236
236
  // Add BMad core option
237
237
  let bmadOptionText;
238
238
  if (state.type === 'v4_existing') {
239
239
  const currentVersion = state.manifest?.version || 'unknown';
240
240
  const newVersion = version; // Always use package.json version
241
- const versionInfo = currentVersion === newVersion
241
+ const versionInfo = currentVersion === newVersion
242
242
  ? `(v${currentVersion} - reinstall)`
243
243
  : `(v${currentVersion} → v${newVersion})`;
244
244
  bmadOptionText = `Update ${coreShortTitle} ${versionInfo} .bmad-core`;
245
245
  } else {
246
246
  bmadOptionText = `${coreShortTitle} (v${version}) .bmad-core`;
247
247
  }
248
-
248
+
249
249
  choices.push({
250
250
  name: bmadOptionText,
251
251
  value: 'bmad-core',
252
252
  checked: true
253
253
  });
254
-
254
+
255
255
  // Add expansion pack options
256
256
  for (const pack of availableExpansionPacks) {
257
257
  const existing = existingExpansionPacks[pack.id];
258
258
  let packOptionText;
259
-
259
+
260
260
  if (existing) {
261
261
  const currentVersion = existing.manifest?.version || 'unknown';
262
262
  const newVersion = pack.version;
263
- const versionInfo = currentVersion === newVersion
263
+ const versionInfo = currentVersion === newVersion
264
264
  ? `(v${currentVersion} - reinstall)`
265
265
  : `(v${currentVersion} → v${newVersion})`;
266
266
  packOptionText = `Update ${pack.shortTitle} ${versionInfo} .${pack.id}`;
267
267
  } else {
268
268
  packOptionText = `${pack.shortTitle} (v${pack.version}) .${pack.id}`;
269
269
  }
270
-
270
+
271
271
  choices.push({
272
272
  name: packOptionText,
273
273
  value: pack.id,
274
274
  checked: false
275
275
  });
276
276
  }
277
-
277
+
278
278
  // Ask what to install
279
279
  const { selectedItems } = await inquirer.prompt([
280
280
  {
@@ -290,7 +290,7 @@ async function promptInstallation() {
290
290
  }
291
291
  }
292
292
  ]);
293
-
293
+
294
294
  // Process selections
295
295
  answers.installType = selectedItems.includes('bmad-core') ? 'full' : 'expansion-only';
296
296
  answers.expansionPacks = selectedItems.filter(item => item !== 'bmad-core');
@@ -299,7 +299,7 @@ async function promptInstallation() {
299
299
  if (selectedItems.includes('bmad-core')) {
300
300
  console.log(chalk.cyan('\n📋 Document Organization Settings'));
301
301
  console.log(chalk.dim('Configure how your project documentation should be organized.\n'));
302
-
302
+
303
303
  // Ask about PRD sharding
304
304
  const { prdSharded } = await inquirer.prompt([
305
305
  {
@@ -310,7 +310,7 @@ async function promptInstallation() {
310
310
  }
311
311
  ]);
312
312
  answers.prdSharded = prdSharded;
313
-
313
+
314
314
  // Ask about architecture sharding
315
315
  const { architectureSharded } = await inquirer.prompt([
316
316
  {
@@ -321,7 +321,7 @@ async function promptInstallation() {
321
321
  }
322
322
  ]);
323
323
  answers.architectureSharded = architectureSharded;
324
-
324
+
325
325
  // Show warning if architecture sharding is disabled
326
326
  if (!architectureSharded) {
327
327
  console.log(chalk.yellow.bold('\n⚠️ IMPORTANT: Architecture Sharding Disabled'));
@@ -330,7 +330,7 @@ async function promptInstallation() {
330
330
  console.log(chalk.yellow('as these are used by the dev agent at runtime.'));
331
331
  console.log(chalk.yellow('\nAlternatively, you can remove these files from the devLoadAlwaysFiles list'));
332
332
  console.log(chalk.yellow('in your core-config.yaml after installation.'));
333
-
333
+
334
334
  const { acknowledge } = await inquirer.prompt([
335
335
  {
336
336
  type: 'confirm',
@@ -339,7 +339,7 @@ async function promptInstallation() {
339
339
  default: false
340
340
  }
341
341
  ]);
342
-
342
+
343
343
  if (!acknowledge) {
344
344
  console.log(chalk.red('Installation cancelled.'));
345
345
  process.exit(0);
@@ -350,14 +350,14 @@ async function promptInstallation() {
350
350
  // Ask for IDE configuration
351
351
  let ides = [];
352
352
  let ideSelectionComplete = false;
353
-
353
+
354
354
  while (!ideSelectionComplete) {
355
355
  console.log(chalk.cyan('\n🛠️ IDE Configuration'));
356
356
  console.log(chalk.bold.yellow.bgRed(' ⚠️ IMPORTANT: This is a MULTISELECT! Use SPACEBAR to toggle each IDE! '));
357
357
  console.log(chalk.bold.magenta('🔸 Use arrow keys to navigate'));
358
358
  console.log(chalk.bold.magenta('🔸 Use SPACEBAR to select/deselect IDEs'));
359
359
  console.log(chalk.bold.magenta('🔸 Press ENTER when finished selecting\n'));
360
-
360
+
361
361
  const ideResponse = await inquirer.prompt([
362
362
  {
363
363
  type: 'checkbox',
@@ -373,11 +373,12 @@ async function promptInstallation() {
373
373
  { name: 'Cline', value: 'cline' },
374
374
  { name: 'Gemini CLI', value: 'gemini' },
375
375
  { name: 'Qwen Code', value: 'qwen-code' },
376
+ { name: 'Crush', value: 'crush' },
376
377
  { name: 'Github Copilot', value: 'github-copilot' }
377
378
  ]
378
379
  }
379
380
  ]);
380
-
381
+
381
382
  ides = ideResponse.ides;
382
383
 
383
384
  // Confirm no IDE selection if none selected
@@ -390,13 +391,13 @@ async function promptInstallation() {
390
391
  default: false
391
392
  }
392
393
  ]);
393
-
394
+
394
395
  if (!confirmNoIde) {
395
396
  console.log(chalk.bold.red('\n🔄 Returning to IDE selection. Remember to use SPACEBAR to select IDEs!\n'));
396
397
  continue; // Go back to IDE selection only
397
398
  }
398
399
  }
399
-
400
+
400
401
  ideSelectionComplete = true;
401
402
  }
402
403
 
@@ -407,7 +408,7 @@ async function promptInstallation() {
407
408
  if (ides.includes('github-copilot')) {
408
409
  console.log(chalk.cyan('\n🔧 GitHub Copilot Configuration'));
409
410
  console.log(chalk.dim('BMad works best with specific VS Code settings for optimal agent experience.\n'));
410
-
411
+
411
412
  const { configChoice } = await inquirer.prompt([
412
413
  {
413
414
  type: 'list',
@@ -430,7 +431,7 @@ async function promptInstallation() {
430
431
  default: 'defaults'
431
432
  }
432
433
  ]);
433
-
434
+
434
435
  answers.githubCopilotConfig = { configChoice };
435
436
  }
436
437
 
@@ -28,6 +28,16 @@ ide-configurations:
28
28
  # To use BMad agents in Claude Code:
29
29
  # 1. Type /agent-name (e.g., "/dev", "/pm", "/architect")
30
30
  # 2. Claude will switch to that agent's persona
31
+ crush:
32
+ name: Crush
33
+ rule-dir: .crush/commands/BMad/
34
+ format: multi-file
35
+ command-suffix: .md
36
+ instructions: |
37
+ # To use BMad agents in Crush:
38
+ # 1. Press CTRL + P and press TAB
39
+ # 2. Select agent or task
40
+ # 3. Crush will switch to that agent's persona / task
31
41
  windsurf:
32
42
  name: Windsurf
33
43
  rule-dir: .windsurf/rules/
@@ -110,4 +120,4 @@ ide-configurations:
110
120
  # 1. The installer creates a .qwen/bmad-method/ directory in your project.
111
121
  # 2. It concatenates all agent files into a single QWEN.md file.
112
122
  # 3. Simply mention the agent in your prompt (e.g., "As *dev, ...").
113
- # 4. The Qwen Code CLI will automatically have the context for that agent.
123
+ # 4. The Qwen Code CLI will automatically have the context for that agent.
@@ -2,7 +2,7 @@ const fs = require("fs-extra");
2
2
  const path = require("path");
3
3
  const crypto = require("crypto");
4
4
  const yaml = require("js-yaml");
5
- const chalk = require("chalk");
5
+ const chalk = require("chalk").default || require("chalk");
6
6
  const { createReadStream, createWriteStream, promises: fsPromises } = require('fs');
7
7
  const { pipeline } = require('stream/promises');
8
8
  const resourceLocator = require('./resource-locator');
@@ -6,7 +6,7 @@
6
6
  const path = require("path");
7
7
  const fs = require("fs-extra");
8
8
  const yaml = require("js-yaml");
9
- const chalk = require("chalk");
9
+ const chalk = require("chalk").default || require("chalk");
10
10
  const fileManager = require("./file-manager");
11
11
  const resourceLocator = require("./resource-locator");
12
12
  const { extractYamlFromAgent } = require("../../lib/yaml-utils");