bmad-method 5.0.0-beta.2 → 5.0.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 (131) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +3 -3
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +3 -3
  3. package/.github/workflows/discord.yaml +11 -2
  4. package/.github/workflows/format-check.yaml +42 -0
  5. package/.github/workflows/manual-release.yaml +173 -0
  6. package/.husky/pre-commit +3 -0
  7. package/.vscode/settings.json +26 -1
  8. package/CHANGELOG.md +0 -11
  9. package/README.md +2 -0
  10. package/bmad-core/agent-teams/team-all.yaml +1 -1
  11. package/bmad-core/agents/bmad-orchestrator.md +1 -1
  12. package/bmad-core/agents/dev.md +4 -4
  13. package/bmad-core/data/bmad-kb.md +1 -1
  14. package/bmad-core/data/test-levels-framework.md +12 -12
  15. package/bmad-core/tasks/facilitate-brainstorming-session.md +1 -1
  16. package/bmad-core/tasks/nfr-assess.md +10 -10
  17. package/bmad-core/tasks/qa-gate.md +23 -23
  18. package/bmad-core/tasks/review-story.md +18 -18
  19. package/bmad-core/tasks/risk-profile.md +25 -25
  20. package/bmad-core/tasks/test-design.md +9 -9
  21. package/bmad-core/tasks/trace-requirements.md +21 -21
  22. package/bmad-core/templates/architecture-tmpl.yaml +49 -49
  23. package/bmad-core/templates/brainstorming-output-tmpl.yaml +5 -5
  24. package/bmad-core/templates/brownfield-architecture-tmpl.yaml +31 -31
  25. package/bmad-core/templates/brownfield-prd-tmpl.yaml +13 -13
  26. package/bmad-core/templates/competitor-analysis-tmpl.yaml +19 -6
  27. package/bmad-core/templates/front-end-architecture-tmpl.yaml +21 -9
  28. package/bmad-core/templates/front-end-spec-tmpl.yaml +24 -24
  29. package/bmad-core/templates/fullstack-architecture-tmpl.yaml +122 -104
  30. package/bmad-core/templates/market-research-tmpl.yaml +2 -2
  31. package/bmad-core/templates/prd-tmpl.yaml +9 -9
  32. package/bmad-core/templates/project-brief-tmpl.yaml +4 -4
  33. package/bmad-core/templates/qa-gate-tmpl.yaml +9 -9
  34. package/bmad-core/templates/story-tmpl.yaml +12 -12
  35. package/bmad-core/workflows/brownfield-fullstack.yaml +9 -9
  36. package/bmad-core/workflows/brownfield-service.yaml +1 -1
  37. package/bmad-core/workflows/brownfield-ui.yaml +1 -1
  38. package/bmad-core/workflows/greenfield-fullstack.yaml +1 -1
  39. package/bmad-core/workflows/greenfield-service.yaml +1 -1
  40. package/bmad-core/workflows/greenfield-ui.yaml +1 -1
  41. package/common/utils/bmad-doc-template.md +5 -5
  42. package/dist/agents/analyst.txt +28 -15
  43. package/dist/agents/architect.txt +220 -190
  44. package/dist/agents/bmad-master.txt +298 -255
  45. package/dist/agents/bmad-orchestrator.txt +1 -1
  46. package/dist/agents/pm.txt +20 -20
  47. package/dist/agents/po.txt +11 -11
  48. package/dist/agents/qa.txt +275 -618
  49. package/dist/agents/sm.txt +11 -11
  50. package/dist/agents/ux-expert.txt +23 -23
  51. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-designer.txt +109 -109
  52. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.txt +75 -77
  53. package/dist/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.txt +41 -41
  54. package/dist/expansion-packs/bmad-2d-phaser-game-dev/teams/phaser-2d-nodejs-game-team.txt +483 -474
  55. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-architect.txt +1 -1
  56. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-designer.txt +149 -149
  57. package/dist/expansion-packs/bmad-2d-unity-game-dev/agents/game-sm.txt +20 -20
  58. package/dist/expansion-packs/bmad-2d-unity-game-dev/teams/unity-2d-game-team.txt +371 -358
  59. package/dist/expansion-packs/bmad-infrastructure-devops/agents/infra-devops-platform.txt +25 -25
  60. package/dist/teams/team-all.txt +581 -881
  61. package/dist/teams/team-fullstack.txt +316 -273
  62. package/dist/teams/team-ide-minimal.txt +276 -619
  63. package/dist/teams/team-no-ui.txt +281 -238
  64. package/docs/versioning-and-releases.md +114 -44
  65. package/eslint.config.mjs +119 -0
  66. package/expansion-packs/Complete AI Agent System - Blank Templates & Google Cloud Setup/PART 1 - Google Cloud Vertex AI Setup Documentation/1.4 Deployment Configuration/1.4.2 - cloudbuild.yaml +26 -26
  67. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-developer.md +4 -4
  68. package/expansion-packs/bmad-2d-phaser-game-dev/agents/game-sm.md +1 -1
  69. package/expansion-packs/bmad-2d-phaser-game-dev/data/development-guidelines.md +26 -28
  70. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-architecture-tmpl.yaml +50 -50
  71. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-brief-tmpl.yaml +23 -23
  72. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-design-doc-tmpl.yaml +24 -24
  73. package/expansion-packs/bmad-2d-phaser-game-dev/templates/game-story-tmpl.yaml +42 -42
  74. package/expansion-packs/bmad-2d-phaser-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
  75. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-dev-greenfield.yaml +5 -5
  76. package/expansion-packs/bmad-2d-phaser-game-dev/workflows/game-prototype.yaml +1 -1
  77. package/expansion-packs/bmad-2d-unity-game-dev/agents/game-developer.md +3 -3
  78. package/expansion-packs/bmad-2d-unity-game-dev/data/bmad-kb.md +1 -1
  79. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-brief-tmpl.yaml +23 -23
  80. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-design-doc-tmpl.yaml +63 -63
  81. package/expansion-packs/bmad-2d-unity-game-dev/templates/game-story-tmpl.yaml +20 -20
  82. package/expansion-packs/bmad-2d-unity-game-dev/templates/level-design-doc-tmpl.yaml +65 -65
  83. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-dev-greenfield.yaml +5 -5
  84. package/expansion-packs/bmad-2d-unity-game-dev/workflows/game-prototype.yaml +1 -1
  85. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-architecture-tmpl.yaml +20 -20
  86. package/expansion-packs/bmad-infrastructure-devops/templates/infrastructure-platform-from-arch-tmpl.yaml +7 -7
  87. package/package.json +62 -39
  88. package/prettier.config.mjs +32 -0
  89. package/release_notes.md +30 -0
  90. package/tools/bmad-npx-wrapper.js +10 -10
  91. package/tools/builders/web-builder.js +124 -130
  92. package/tools/bump-all-versions.js +42 -33
  93. package/tools/bump-expansion-version.js +23 -16
  94. package/tools/cli.js +10 -12
  95. package/tools/flattener/aggregate.js +10 -10
  96. package/tools/flattener/binary.js +44 -17
  97. package/tools/flattener/discovery.js +19 -18
  98. package/tools/flattener/files.js +6 -6
  99. package/tools/flattener/ignoreRules.js +125 -125
  100. package/tools/flattener/main.js +201 -304
  101. package/tools/flattener/projectRoot.js +75 -73
  102. package/tools/flattener/prompts.js +9 -9
  103. package/tools/flattener/stats.helpers.js +131 -67
  104. package/tools/flattener/stats.js +3 -3
  105. package/tools/flattener/test-matrix.js +201 -193
  106. package/tools/flattener/xml.js +33 -31
  107. package/tools/installer/bin/bmad.js +130 -89
  108. package/tools/installer/config/ide-agent-config.yaml +1 -1
  109. package/tools/installer/config/install.config.yaml +2 -2
  110. package/tools/installer/lib/config-loader.js +46 -42
  111. package/tools/installer/lib/file-manager.js +91 -113
  112. package/tools/installer/lib/ide-base-setup.js +57 -56
  113. package/tools/installer/lib/ide-setup.js +375 -343
  114. package/tools/installer/lib/installer.js +875 -714
  115. package/tools/installer/lib/memory-profiler.js +54 -53
  116. package/tools/installer/lib/module-manager.js +19 -15
  117. package/tools/installer/lib/resource-locator.js +26 -28
  118. package/tools/installer/package.json +19 -19
  119. package/tools/lib/dependency-resolver.js +26 -30
  120. package/tools/lib/yaml-utils.js +7 -7
  121. package/tools/preview-release-notes.js +66 -0
  122. package/tools/shared/bannerArt.js +3 -3
  123. package/tools/sync-installer-version.js +7 -9
  124. package/tools/update-expansion-version.js +14 -15
  125. package/tools/upgraders/v3-to-v4-upgrader.js +203 -294
  126. package/tools/version-bump.js +41 -26
  127. package/tools/yaml-format.js +56 -43
  128. package/.github/workflows/promote-to-stable.yml +0 -144
  129. package/.github/workflows/release.yaml +0 -60
  130. package/.releaserc.json +0 -21
  131. package/tools/semantic-release-sync-installer.js +0 -30
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  /* deno-lint-ignore-file */
3
2
  /*
4
3
  Automatic test matrix for project root detection.
@@ -6,65 +5,65 @@
6
5
  No external options or flags required. Safe to run multiple times.
7
6
  */
8
7
 
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");
8
+ const os = require('node:os');
9
+ const path = require('node:path');
10
+ const fs = require('fs-extra');
11
+ const { promisify } = require('node:util');
12
+ const { execFile } = require('node:child_process');
13
+ const process = require('node:process');
15
14
  const execFileAsync = promisify(execFile);
16
15
 
17
- const { findProjectRoot } = require("./projectRoot.js");
16
+ const { findProjectRoot } = require('./projectRoot.js');
18
17
 
19
18
  async function cmdAvailable(cmd) {
20
19
  try {
21
- await execFileAsync(cmd, ["--version"], { timeout: 500, windowsHide: true });
20
+ await execFileAsync(cmd, ['--version'], { timeout: 500, windowsHide: true });
22
21
  return true;
23
22
  } catch {
24
23
  return false;
25
24
  }
26
25
 
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
- }
26
+ async function testSvnMarker() {
27
+ const root = await mkTmpDir('svn');
28
+ const nested = path.join(root, 'proj', 'code');
29
+ await fs.ensureDir(nested);
30
+ await fs.ensureDir(path.join(root, '.svn'));
31
+ const found = await findProjectRoot(nested);
32
+ assertEqual(found, root, '.svn marker should be detected');
33
+ return { name: 'svn-marker', ok: true };
34
+ }
36
35
 
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 };
36
+ async function testSymlinkStart() {
37
+ const root = await mkTmpDir('symlink-start');
38
+ const nested = path.join(root, 'a', 'b');
39
+ await fs.ensureDir(nested);
40
+ await fs.writeFile(path.join(root, '.project-root'), '\n');
41
+ const tmp = await mkTmpDir('symlink-tmp');
42
+ const link = path.join(tmp, 'link-to-b');
43
+ try {
44
+ await fs.symlink(nested, link);
45
+ } catch {
46
+ // symlink may not be permitted on some systems; skip
47
+ return { name: 'symlink-start', ok: true, skipped: true };
48
+ }
49
+ const found = await findProjectRoot(link);
50
+ assertEqual(found, root, 'should resolve symlinked start to real root');
51
+ return { name: 'symlink-start', ok: true };
49
52
  }
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
53
 
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
- }
54
+ async function testSubmoduleLikeInnerGitFile() {
55
+ const root = await mkTmpDir('submodule-like');
56
+ const mid = path.join(root, 'mid');
57
+ const leaf = path.join(mid, 'leaf');
58
+ await fs.ensureDir(leaf);
59
+ // outer repo
60
+ await fs.ensureDir(path.join(root, '.git'));
61
+ // inner submodule-like .git file
62
+ await fs.writeFile(path.join(mid, '.git'), 'gitdir: ../.git/modules/mid\n');
63
+ const found = await findProjectRoot(leaf);
64
+ assertEqual(found, root, 'outermost .git should win on tie weight');
65
+ return { name: 'submodule-like-gitfile', ok: true };
66
+ }
68
67
  }
69
68
 
70
69
  async function mkTmpDir(name) {
@@ -75,274 +74,283 @@ async function mkTmpDir(name) {
75
74
 
76
75
  function assertEqual(actual, expected, msg) {
77
76
  if (actual !== expected) {
78
- throw new Error(`${msg}: expected=\"${expected}\" actual=\"${actual}\"`);
77
+ throw new Error(`${msg}: expected="${expected}" actual="${actual}"`);
79
78
  }
80
79
  }
81
80
 
82
81
  async function testSentinel() {
83
- const root = await mkTmpDir("sentinel");
84
- const nested = path.join(root, "a", "b", "c");
82
+ const root = await mkTmpDir('sentinel');
83
+ const nested = path.join(root, 'a', 'b', 'c');
85
84
  await fs.ensureDir(nested);
86
- await fs.writeFile(path.join(root, ".project-root"), "\n");
85
+ await fs.writeFile(path.join(root, '.project-root'), '\n');
87
86
  const found = await findProjectRoot(nested);
88
- await assertEqual(found, root, "sentinel .project-root should win");
89
- return { name: "sentinel", ok: true };
87
+ await assertEqual(found, root, 'sentinel .project-root should win');
88
+ return { name: 'sentinel', ok: true };
90
89
  }
91
90
 
92
91
  async function testOtherSentinels() {
93
- const root = await mkTmpDir("other-sentinels");
94
- const nested = path.join(root, "x", "y");
92
+ const root = await mkTmpDir('other-sentinels');
93
+ const nested = path.join(root, 'x', 'y');
95
94
  await fs.ensureDir(nested);
96
- await fs.writeFile(path.join(root, ".workspace-root"), "\n");
95
+ await fs.writeFile(path.join(root, '.workspace-root'), '\n');
97
96
  const found1 = await findProjectRoot(nested);
98
- assertEqual(found1, root, "sentinel .workspace-root should win");
97
+ assertEqual(found1, root, 'sentinel .workspace-root should win');
99
98
 
100
- await fs.remove(path.join(root, ".workspace-root"));
101
- await fs.writeFile(path.join(root, ".repo-root"), "\n");
99
+ await fs.remove(path.join(root, '.workspace-root'));
100
+ await fs.writeFile(path.join(root, '.repo-root'), '\n');
102
101
  const found2 = await findProjectRoot(nested);
103
- assertEqual(found2, root, "sentinel .repo-root should win");
104
- return { name: "other-sentinels", ok: true };
102
+ assertEqual(found2, root, 'sentinel .repo-root should win');
103
+ return { name: 'other-sentinels', ok: true };
105
104
  }
106
105
 
107
106
  async function testGitCliAndMarker() {
108
- const hasGit = await cmdAvailable("git");
109
- if (!hasGit) return { name: "git-cli", ok: true, skipped: true };
107
+ const hasGit = await cmdAvailable('git');
108
+ if (!hasGit) return { name: 'git-cli', ok: true, skipped: true };
110
109
 
111
- const root = await mkTmpDir("git");
112
- const nested = path.join(root, "pkg", "src");
110
+ const root = await mkTmpDir('git');
111
+ const nested = path.join(root, 'pkg', 'src');
113
112
  await fs.ensureDir(nested);
114
- await execFileAsync("git", ["init"], { cwd: root, timeout: 2000 });
113
+ await execFileAsync('git', ['init'], { cwd: root, timeout: 2000 });
115
114
  const found = await findProjectRoot(nested);
116
- await assertEqual(found, root, "git toplevel should be detected");
117
- return { name: "git-cli", ok: true };
115
+ await assertEqual(found, root, 'git toplevel should be detected');
116
+ return { name: 'git-cli', ok: true };
118
117
  }
119
118
 
120
119
  async function testHgMarkerOrCli() {
121
120
  // Prefer simple marker test to avoid requiring Mercurial install
122
- const root = await mkTmpDir("hg");
123
- const nested = path.join(root, "lib");
121
+ const root = await mkTmpDir('hg');
122
+ const nested = path.join(root, 'lib');
124
123
  await fs.ensureDir(nested);
125
- await fs.ensureDir(path.join(root, ".hg"));
124
+ await fs.ensureDir(path.join(root, '.hg'));
126
125
  const found = await findProjectRoot(nested);
127
- await assertEqual(found, root, ".hg marker should be detected");
128
- return { name: "hg-marker", ok: true };
126
+ await assertEqual(found, root, '.hg marker should be detected');
127
+ return { name: 'hg-marker', ok: true };
129
128
  }
130
129
 
131
130
  async function testWorkspacePnpm() {
132
- const root = await mkTmpDir("pnpm-workspace");
133
- const pkgA = path.join(root, "packages", "a");
131
+ const root = await mkTmpDir('pnpm-workspace');
132
+ const pkgA = path.join(root, 'packages', 'a');
134
133
  await fs.ensureDir(pkgA);
135
- await fs.writeFile(path.join(root, "pnpm-workspace.yaml"), "packages:\n - packages/*\n");
134
+ await fs.writeFile(path.join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n');
136
135
  const found = await findProjectRoot(pkgA);
137
- await assertEqual(found, root, "pnpm-workspace.yaml should be detected");
138
- return { name: "pnpm-workspace", ok: true };
136
+ await assertEqual(found, root, 'pnpm-workspace.yaml should be detected');
137
+ return { name: 'pnpm-workspace', ok: true };
139
138
  }
140
139
 
141
140
  async function testPackageJsonWorkspaces() {
142
- const root = await mkTmpDir("package-workspaces");
143
- const pkgA = path.join(root, "packages", "a");
141
+ const root = await mkTmpDir('package-workspaces');
142
+ const pkgA = path.join(root, 'packages', 'a');
144
143
  await fs.ensureDir(pkgA);
145
- await fs.writeJson(path.join(root, "package.json"), { private: true, workspaces: ["packages/*"] }, { spaces: 2 });
144
+ await fs.writeJson(
145
+ path.join(root, 'package.json'),
146
+ { private: true, workspaces: ['packages/*'] },
147
+ { spaces: 2 },
148
+ );
146
149
  const found = await findProjectRoot(pkgA);
147
- await assertEqual(found, root, "package.json workspaces should be detected");
148
- return { name: "package.json-workspaces", ok: true };
150
+ await assertEqual(found, root, 'package.json workspaces should be detected');
151
+ return { name: 'package.json-workspaces', ok: true };
149
152
  }
150
153
 
151
154
  async function testLockfiles() {
152
- const root = await mkTmpDir("lockfiles");
153
- const nested = path.join(root, "src");
155
+ const root = await mkTmpDir('lockfiles');
156
+ const nested = path.join(root, 'src');
154
157
  await fs.ensureDir(nested);
155
- await fs.writeFile(path.join(root, "yarn.lock"), "\n");
158
+ await fs.writeFile(path.join(root, 'yarn.lock'), '\n');
156
159
  const found = await findProjectRoot(nested);
157
- await assertEqual(found, root, "yarn.lock should be detected");
158
- return { name: "lockfiles", ok: true };
160
+ await assertEqual(found, root, 'yarn.lock should be detected');
161
+ return { name: 'lockfiles', ok: true };
159
162
  }
160
163
 
161
164
  async function testLanguageConfigs() {
162
- const root = await mkTmpDir("lang-configs");
163
- const nested = path.join(root, "x", "y");
165
+ const root = await mkTmpDir('lang-configs');
166
+ const nested = path.join(root, 'x', 'y');
164
167
  await fs.ensureDir(nested);
165
- await fs.writeFile(path.join(root, "pyproject.toml"), "[tool.poetry]\nname='tmp'\n");
168
+ await fs.writeFile(path.join(root, 'pyproject.toml'), "[tool.poetry]\nname='tmp'\n");
166
169
  const found = await findProjectRoot(nested);
167
- await assertEqual(found, root, "pyproject.toml should be detected");
168
- return { name: "language-configs", ok: true };
170
+ await assertEqual(found, root, 'pyproject.toml should be detected');
171
+ return { name: 'language-configs', ok: true };
169
172
  }
170
173
 
171
174
  async function testPreferOuterOnTie() {
172
- const root = await mkTmpDir("tie");
173
- const mid = path.join(root, "mid");
174
- const leaf = path.join(mid, "leaf");
175
+ const root = await mkTmpDir('tie');
176
+ const mid = path.join(root, 'mid');
177
+ const leaf = path.join(mid, 'leaf');
175
178
  await fs.ensureDir(leaf);
176
179
  // 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");
180
+ await fs.writeFile(path.join(root, 'requirements.txt'), '\n');
181
+ await fs.writeFile(path.join(mid, 'requirements.txt'), '\n');
179
182
  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 };
183
+ await assertEqual(found, root, 'outermost directory should win on equal weight');
184
+ return { name: 'prefer-outermost-tie', ok: true };
182
185
  }
183
186
 
184
187
  // Additional coverage: Bazel, Nx/Turbo/Rush, Go workspaces, Deno, Java/Scala, PHP, Rust, Nix, Changesets, env markers,
185
188
  // and priority interaction between package.json and lockfiles.
186
189
 
187
190
  async function testBazelWorkspace() {
188
- const root = await mkTmpDir("bazel");
189
- const nested = path.join(root, "apps", "svc");
191
+ const root = await mkTmpDir('bazel');
192
+ const nested = path.join(root, 'apps', 'svc');
190
193
  await fs.ensureDir(nested);
191
- await fs.writeFile(path.join(root, "WORKSPACE"), "workspace(name=\"tmp\")\n");
194
+ await fs.writeFile(path.join(root, 'WORKSPACE'), 'workspace(name="tmp")\n');
192
195
  const found = await findProjectRoot(nested);
193
- await assertEqual(found, root, "Bazel WORKSPACE should be detected");
194
- return { name: "bazel-workspace", ok: true };
196
+ await assertEqual(found, root, 'Bazel WORKSPACE should be detected');
197
+ return { name: 'bazel-workspace', ok: true };
195
198
  }
196
199
 
197
200
  async function testNx() {
198
- const root = await mkTmpDir("nx");
199
- const nested = path.join(root, "apps", "web");
201
+ const root = await mkTmpDir('nx');
202
+ const nested = path.join(root, 'apps', 'web');
200
203
  await fs.ensureDir(nested);
201
- await fs.writeJson(path.join(root, "nx.json"), { npmScope: "tmp" }, { spaces: 2 });
204
+ await fs.writeJson(path.join(root, 'nx.json'), { npmScope: 'tmp' }, { spaces: 2 });
202
205
  const found = await findProjectRoot(nested);
203
- await assertEqual(found, root, "nx.json should be detected");
204
- return { name: "nx", ok: true };
206
+ await assertEqual(found, root, 'nx.json should be detected');
207
+ return { name: 'nx', ok: true };
205
208
  }
206
209
 
207
210
  async function testTurbo() {
208
- const root = await mkTmpDir("turbo");
209
- const nested = path.join(root, "packages", "x");
211
+ const root = await mkTmpDir('turbo');
212
+ const nested = path.join(root, 'packages', 'x');
210
213
  await fs.ensureDir(nested);
211
- await fs.writeJson(path.join(root, "turbo.json"), { pipeline: {} }, { spaces: 2 });
214
+ await fs.writeJson(path.join(root, 'turbo.json'), { pipeline: {} }, { spaces: 2 });
212
215
  const found = await findProjectRoot(nested);
213
- await assertEqual(found, root, "turbo.json should be detected");
214
- return { name: "turbo", ok: true };
216
+ await assertEqual(found, root, 'turbo.json should be detected');
217
+ return { name: 'turbo', ok: true };
215
218
  }
216
219
 
217
220
  async function testRush() {
218
- const root = await mkTmpDir("rush");
219
- const nested = path.join(root, "apps", "a");
221
+ const root = await mkTmpDir('rush');
222
+ const nested = path.join(root, 'apps', 'a');
220
223
  await fs.ensureDir(nested);
221
- await fs.writeJson(path.join(root, "rush.json"), { projectFolderMinDepth: 1 }, { spaces: 2 });
224
+ await fs.writeJson(path.join(root, 'rush.json'), { projectFolderMinDepth: 1 }, { spaces: 2 });
222
225
  const found = await findProjectRoot(nested);
223
- await assertEqual(found, root, "rush.json should be detected");
224
- return { name: "rush", ok: true };
226
+ await assertEqual(found, root, 'rush.json should be detected');
227
+ return { name: 'rush', ok: true };
225
228
  }
226
229
 
227
230
  async function testGoWorkAndMod() {
228
- const root = await mkTmpDir("gowork");
229
- const mod = path.join(root, "modA");
230
- const nested = path.join(mod, "pkg");
231
+ const root = await mkTmpDir('gowork');
232
+ const mod = path.join(root, 'modA');
233
+ const nested = path.join(mod, 'pkg');
231
234
  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");
235
+ await fs.writeFile(path.join(root, 'go.work'), 'go 1.22\nuse ./modA\n');
236
+ await fs.writeFile(path.join(mod, 'go.mod'), 'module example.com/a\ngo 1.22\n');
234
237
  const found = await findProjectRoot(nested);
235
- await assertEqual(found, root, "go.work should define the workspace root");
236
- return { name: "go-work", ok: true };
238
+ await assertEqual(found, root, 'go.work should define the workspace root');
239
+ return { name: 'go-work', ok: true };
237
240
  }
238
241
 
239
242
  async function testDenoJson() {
240
- const root = await mkTmpDir("deno");
241
- const nested = path.join(root, "src");
243
+ const root = await mkTmpDir('deno');
244
+ const nested = path.join(root, 'src');
242
245
  await fs.ensureDir(nested);
243
- await fs.writeJson(path.join(root, "deno.json"), { tasks: {} }, { spaces: 2 });
246
+ await fs.writeJson(path.join(root, 'deno.json'), { tasks: {} }, { spaces: 2 });
244
247
  const found = await findProjectRoot(nested);
245
- await assertEqual(found, root, "deno.json should be detected");
246
- return { name: "deno-json", ok: true };
248
+ await assertEqual(found, root, 'deno.json should be detected');
249
+ return { name: 'deno-json', ok: true };
247
250
  }
248
251
 
249
252
  async function testGradleSettings() {
250
- const root = await mkTmpDir("gradle");
251
- const nested = path.join(root, "app");
253
+ const root = await mkTmpDir('gradle');
254
+ const nested = path.join(root, 'app');
252
255
  await fs.ensureDir(nested);
253
- await fs.writeFile(path.join(root, "settings.gradle"), "rootProject.name='tmp'\n");
256
+ await fs.writeFile(path.join(root, 'settings.gradle'), "rootProject.name='tmp'\n");
254
257
  const found = await findProjectRoot(nested);
255
- await assertEqual(found, root, "settings.gradle should be detected");
256
- return { name: "gradle-settings", ok: true };
258
+ await assertEqual(found, root, 'settings.gradle should be detected');
259
+ return { name: 'gradle-settings', ok: true };
257
260
  }
258
261
 
259
262
  async function testMavenPom() {
260
- const root = await mkTmpDir("maven");
261
- const nested = path.join(root, "module");
263
+ const root = await mkTmpDir('maven');
264
+ const nested = path.join(root, 'module');
262
265
  await fs.ensureDir(nested);
263
- await fs.writeFile(path.join(root, "pom.xml"), "<project></project>\n");
266
+ await fs.writeFile(path.join(root, 'pom.xml'), '<project></project>\n');
264
267
  const found = await findProjectRoot(nested);
265
- await assertEqual(found, root, "pom.xml should be detected");
266
- return { name: "maven-pom", ok: true };
268
+ await assertEqual(found, root, 'pom.xml should be detected');
269
+ return { name: 'maven-pom', ok: true };
267
270
  }
268
271
 
269
272
  async function testSbtBuild() {
270
- const root = await mkTmpDir("sbt");
271
- const nested = path.join(root, "sub");
273
+ const root = await mkTmpDir('sbt');
274
+ const nested = path.join(root, 'sub');
272
275
  await fs.ensureDir(nested);
273
- await fs.writeFile(path.join(root, "build.sbt"), "name := \"tmp\"\n");
276
+ await fs.writeFile(path.join(root, 'build.sbt'), 'name := "tmp"\n');
274
277
  const found = await findProjectRoot(nested);
275
- await assertEqual(found, root, "build.sbt should be detected");
276
- return { name: "sbt-build", ok: true };
278
+ await assertEqual(found, root, 'build.sbt should be detected');
279
+ return { name: 'sbt-build', ok: true };
277
280
  }
278
281
 
279
282
  async function testComposer() {
280
- const root = await mkTmpDir("composer");
281
- const nested = path.join(root, "src");
283
+ const root = await mkTmpDir('composer');
284
+ const nested = path.join(root, 'src');
282
285
  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");
286
+ await fs.writeJson(path.join(root, 'composer.json'), { name: 'tmp/pkg' }, { spaces: 2 });
287
+ await fs.writeFile(path.join(root, 'composer.lock'), '{}\n');
285
288
  const found = await findProjectRoot(nested);
286
- await assertEqual(found, root, "composer.{json,lock} should be detected");
287
- return { name: "composer", ok: true };
289
+ await assertEqual(found, root, 'composer.{json,lock} should be detected');
290
+ return { name: 'composer', ok: true };
288
291
  }
289
292
 
290
293
  async function testCargo() {
291
- const root = await mkTmpDir("cargo");
292
- const nested = path.join(root, "src");
294
+ const root = await mkTmpDir('cargo');
295
+ const nested = path.join(root, 'src');
293
296
  await fs.ensureDir(nested);
294
- await fs.writeFile(path.join(root, "Cargo.toml"), "[package]\nname='tmp'\nversion='0.0.0'\n");
297
+ await fs.writeFile(path.join(root, 'Cargo.toml'), "[package]\nname='tmp'\nversion='0.0.0'\n");
295
298
  const found = await findProjectRoot(nested);
296
- await assertEqual(found, root, "Cargo.toml should be detected");
297
- return { name: "cargo", ok: true };
299
+ await assertEqual(found, root, 'Cargo.toml should be detected');
300
+ return { name: 'cargo', ok: true };
298
301
  }
299
302
 
300
303
  async function testNixFlake() {
301
- const root = await mkTmpDir("nix");
302
- const nested = path.join(root, "work");
304
+ const root = await mkTmpDir('nix');
305
+ const nested = path.join(root, 'work');
303
306
  await fs.ensureDir(nested);
304
- await fs.writeFile(path.join(root, "flake.nix"), "{ }\n");
307
+ await fs.writeFile(path.join(root, 'flake.nix'), '{ }\n');
305
308
  const found = await findProjectRoot(nested);
306
- await assertEqual(found, root, "flake.nix should be detected");
307
- return { name: "nix-flake", ok: true };
309
+ await assertEqual(found, root, 'flake.nix should be detected');
310
+ return { name: 'nix-flake', ok: true };
308
311
  }
309
312
 
310
313
  async function testChangesetConfig() {
311
- const root = await mkTmpDir("changeset");
312
- const nested = path.join(root, "pkg");
314
+ const root = await mkTmpDir('changeset');
315
+ const nested = path.join(root, 'pkg');
313
316
  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 });
317
+ await fs.ensureDir(path.join(root, '.changeset'));
318
+ await fs.writeJson(
319
+ path.join(root, '.changeset', 'config.json'),
320
+ { $schema: 'https://unpkg.com/@changesets/config@2.3.1/schema.json' },
321
+ { spaces: 2 },
322
+ );
316
323
  const found = await findProjectRoot(nested);
317
- await assertEqual(found, root, ".changeset/config.json should be detected");
318
- return { name: "changesets", ok: true };
324
+ await assertEqual(found, root, '.changeset/config.json should be detected');
325
+ return { name: 'changesets', ok: true };
319
326
  }
320
327
 
321
328
  async function testEnvCustomMarker() {
322
- const root = await mkTmpDir("env-marker");
323
- const nested = path.join(root, "dir");
329
+ const root = await mkTmpDir('env-marker');
330
+ const nested = path.join(root, 'dir');
324
331
  await fs.ensureDir(nested);
325
- await fs.writeFile(path.join(root, "MY_ROOT"), "\n");
332
+ await fs.writeFile(path.join(root, 'MY_ROOT'), '\n');
326
333
  const prev = process.env.PROJECT_ROOT_MARKERS;
327
- process.env.PROJECT_ROOT_MARKERS = "MY_ROOT";
334
+ process.env.PROJECT_ROOT_MARKERS = 'MY_ROOT';
328
335
  try {
329
336
  const found = await findProjectRoot(nested);
330
- await assertEqual(found, root, "custom env marker should be honored");
337
+ await assertEqual(found, root, 'custom env marker should be honored');
331
338
  } finally {
332
- if (prev === undefined) delete process.env.PROJECT_ROOT_MARKERS; else process.env.PROJECT_ROOT_MARKERS = prev;
339
+ if (prev === undefined) delete process.env.PROJECT_ROOT_MARKERS;
340
+ else process.env.PROJECT_ROOT_MARKERS = prev;
333
341
  }
334
- return { name: "env-custom-marker", ok: true };
342
+ return { name: 'env-custom-marker', ok: true };
335
343
  }
336
344
 
337
345
  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
+ const root = await mkTmpDir('pkg-vs-lock');
347
+ const nested = path.join(root, 'nested');
348
+ await fs.ensureDir(path.join(nested, 'deep'));
349
+ await fs.writeJson(path.join(nested, 'package.json'), { name: 'nested' }, { spaces: 2 });
350
+ await fs.writeFile(path.join(root, 'yarn.lock'), '\n');
351
+ const found = await findProjectRoot(path.join(nested, 'deep'));
352
+ await assertEqual(found, root, 'lockfile at root should outrank nested package.json');
353
+ return { name: 'package-vs-lock-priority', ok: true };
346
354
  }
347
355
 
348
356
  async function run() {
@@ -381,25 +389,25 @@ async function run() {
381
389
  try {
382
390
  const r = await t();
383
391
  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) });
392
+ console.log(`✔ ${r.name}${r.skipped ? ' (skipped)' : ''}`);
393
+ } catch (error) {
394
+ console.error(`✖ ${t.name}:`, error && error.message ? error.message : error);
395
+ results.push({ name: t.name, ok: false, error: String(error) });
388
396
  }
389
397
  }
390
398
 
391
399
  const failed = results.filter((r) => !r.ok);
392
- console.log("\nSummary:");
400
+ console.log('\nSummary:');
393
401
  for (const r of results) {
394
- console.log(`- ${r.name}: ${r.ok ? "ok" : "FAIL"}${r.skipped ? " (skipped)" : ""}`);
402
+ console.log(`- ${r.name}: ${r.ok ? 'ok' : 'FAIL'}${r.skipped ? ' (skipped)' : ''}`);
395
403
  }
396
404
 
397
- if (failed.length) {
405
+ if (failed.length > 0) {
398
406
  process.exitCode = 1;
399
407
  }
400
408
  }
401
409
 
402
- run().catch((e) => {
403
- console.error("Fatal error:", e);
410
+ run().catch((error) => {
411
+ console.error('Fatal error:', error);
404
412
  process.exit(1);
405
413
  });