@zeyue0329/xiaoma-cli 1.0.36 → 1.0.38

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 (89) hide show
  1. package/.idea/workspace.xml +27 -26
  2. package/JAVA-BACKEND-COMMANDS-REFERENCE.md +62 -52
  3. package/JAVA-BACKEND-ITERATION-GUIDE.md +125 -18
  4. package/README.md +1 -1
  5. package/common/utils/bmad-doc-template.md +5 -5
  6. package/dist/agents/analyst.txt +35 -5
  7. package/dist/agents/architect.txt +217 -31
  8. package/dist/agents/automation-orchestrator.txt +4 -4
  9. package/dist/agents/dev.txt +3 -3
  10. package/dist/agents/full-requirement-orchestrator.txt +11 -11
  11. package/dist/agents/qa.txt +102 -102
  12. package/dist/agents/sm.txt +6 -6
  13. package/dist/agents/ux-expert.txt +6 -1
  14. package/dist/agents/workflow-executor.txt +879 -0
  15. package/dist/agents/xiaoma-master.txt +258 -37
  16. package/dist/teams/team-all.txt +1223 -445
  17. package/dist/teams/team-fullstack-with-database.txt +384 -446
  18. package/dist/teams/team-fullstack.txt +258 -37
  19. package/dist/teams/team-ide-minimal.txt +111 -111
  20. package/dist/teams/team-no-ui.txt +252 -36
  21. package/docs/architecture-sharding-modification.md +623 -0
  22. package/docs/automated-requirements-analysis-outputs.md +896 -0
  23. package/package.json +1 -1
  24. package/tools/builders/web-builder.js +292 -142
  25. package/tools/bump-all-versions.js +50 -32
  26. package/tools/cli.js +52 -47
  27. package/tools/flattener/aggregate.js +30 -12
  28. package/tools/flattener/binary.js +46 -43
  29. package/tools/flattener/discovery.js +23 -15
  30. package/tools/flattener/files.js +6 -6
  31. package/tools/flattener/ignoreRules.js +122 -121
  32. package/tools/flattener/main.js +249 -144
  33. package/tools/flattener/projectRoot.js +74 -69
  34. package/tools/flattener/prompts.js +12 -10
  35. package/tools/flattener/stats.helpers.js +90 -61
  36. package/tools/flattener/stats.js +1 -1
  37. package/tools/flattener/test-matrix.js +225 -170
  38. package/tools/flattener/xml.js +31 -23
  39. package/tools/installer/bin/xiaoma.js +199 -153
  40. package/tools/installer/lib/config-loader.js +76 -47
  41. package/tools/installer/lib/file-manager.js +101 -44
  42. package/tools/installer/lib/ide-base-setup.js +49 -39
  43. package/tools/installer/lib/ide-setup.js +694 -380
  44. package/tools/installer/lib/installer.js +802 -469
  45. package/tools/installer/lib/memory-profiler.js +22 -12
  46. package/tools/installer/lib/module-manager.js +16 -14
  47. package/tools/installer/lib/resource-locator.js +61 -35
  48. package/tools/lib/dependency-resolver.js +34 -23
  49. package/tools/lib/yaml-utils.js +7 -2
  50. package/tools/preview-release-notes.js +33 -25
  51. package/tools/shared/bannerArt.js +3 -3
  52. package/tools/sync-installer-version.js +16 -7
  53. package/tools/upgraders/v3-to-v4-upgrader.js +244 -163
  54. package/tools/version-bump.js +24 -18
  55. package/tools/xiaoma-npx-wrapper.js +15 -10
  56. package/tools/yaml-format.js +60 -36
  57. package/xiaoma-core/agent-teams/team-fullstack-with-database.yaml +0 -1
  58. package/xiaoma-core/agents/automated-fix-validator.yaml +2 -1
  59. package/xiaoma-core/agents/automated-quality-validator.yaml +10 -5
  60. package/xiaoma-core/agents/automation-orchestrator.md +4 -4
  61. package/xiaoma-core/agents/dev.md +4 -4
  62. package/xiaoma-core/agents/enhanced-workflow-orchestrator.yaml +2 -1
  63. package/xiaoma-core/agents/full-requirement-orchestrator.md +11 -11
  64. package/xiaoma-core/agents/global-requirements-auditor.yaml +11 -3
  65. package/xiaoma-core/agents/intelligent-template-adapter.yaml +19 -5
  66. package/xiaoma-core/agents/master-execution-engine.yaml +19 -5
  67. package/xiaoma-core/agents/workflow-executor.md +126 -18
  68. package/xiaoma-core/agents/xiaoma-master.md +1 -1
  69. package/xiaoma-core/data/test-levels-framework.md +12 -12
  70. package/xiaoma-core/tasks/analyze-existing-database.md +1 -1
  71. package/xiaoma-core/tasks/apply-qa-fixes.md +3 -3
  72. package/xiaoma-core/tasks/batch-story-generation.md +22 -22
  73. package/xiaoma-core/tasks/create-enhanced-story-with-database.md +6 -6
  74. package/xiaoma-core/tasks/nfr-assess.md +6 -6
  75. package/xiaoma-core/tasks/project-integration-testing.md +42 -42
  76. package/xiaoma-core/tasks/qa-gate.md +23 -23
  77. package/xiaoma-core/tasks/review-story.md +18 -18
  78. package/xiaoma-core/tasks/risk-profile.md +25 -25
  79. package/xiaoma-core/tasks/serial-development-orchestration.md +51 -51
  80. package/xiaoma-core/tasks/test-design.md +9 -9
  81. package/xiaoma-core/tasks/trace-requirements.md +21 -21
  82. package/xiaoma-core/templates/competitor-analysis-tmpl.yaml +35 -5
  83. package/xiaoma-core/templates/front-end-architecture-tmpl.yaml +77 -11
  84. package/xiaoma-core/templates/front-end-spec-tmpl.yaml +6 -1
  85. package/xiaoma-core/templates/fullstack-architecture-tmpl.yaml +140 -20
  86. package/xiaoma-core/templates/global-qa-monitoring-tmpl.yaml +2 -1
  87. package/xiaoma-core/templates/requirements-coverage-audit.yaml +2 -1
  88. package/xiaoma-core/workflows/automated-requirements-analysis.yaml +4 -4
  89. package/dist/agents/database-architect.txt +0 -322
@@ -5,64 +5,67 @@
5
5
  No external options or flags required. Safe to run multiple times.
6
6
  */
7
7
 
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');
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");
14
14
  const execFileAsync = promisify(execFile);
15
15
 
16
- const { findProjectRoot } = require('./projectRoot.js');
16
+ const { findProjectRoot } = require("./projectRoot.js");
17
17
 
18
18
  async function cmdAvailable(cmd) {
19
19
  try {
20
- await execFileAsync(cmd, ['--version'], { timeout: 500, windowsHide: true });
20
+ await execFileAsync(cmd, ["--version"], {
21
+ timeout: 500,
22
+ windowsHide: true,
23
+ });
21
24
  return true;
22
25
  } catch {
23
26
  return false;
24
27
  }
25
28
 
26
29
  async function testSvnMarker() {
27
- const root = await mkTmpDir('svn');
28
- const nested = path.join(root, 'proj', 'code');
30
+ const root = await mkTmpDir("svn");
31
+ const nested = path.join(root, "proj", "code");
29
32
  await fs.ensureDir(nested);
30
- await fs.ensureDir(path.join(root, '.svn'));
33
+ await fs.ensureDir(path.join(root, ".svn"));
31
34
  const found = await findProjectRoot(nested);
32
- assertEqual(found, root, '.svn marker should be detected');
33
- return { name: 'svn-marker', ok: true };
35
+ assertEqual(found, root, ".svn marker should be detected");
36
+ return { name: "svn-marker", ok: true };
34
37
  }
35
38
 
36
39
  async function testSymlinkStart() {
37
- const root = await mkTmpDir('symlink-start');
38
- const nested = path.join(root, 'a', 'b');
40
+ const root = await mkTmpDir("symlink-start");
41
+ const nested = path.join(root, "a", "b");
39
42
  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
+ await fs.writeFile(path.join(root, ".project-root"), "\n");
44
+ const tmp = await mkTmpDir("symlink-tmp");
45
+ const link = path.join(tmp, "link-to-b");
43
46
  try {
44
47
  await fs.symlink(nested, link);
45
48
  } catch {
46
49
  // symlink may not be permitted on some systems; skip
47
- return { name: 'symlink-start', ok: true, skipped: true };
50
+ return { name: "symlink-start", ok: true, skipped: true };
48
51
  }
49
52
  const found = await findProjectRoot(link);
50
- assertEqual(found, root, 'should resolve symlinked start to real root');
51
- return { name: 'symlink-start', ok: true };
53
+ assertEqual(found, root, "should resolve symlinked start to real root");
54
+ return { name: "symlink-start", ok: true };
52
55
  }
53
56
 
54
57
  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
+ const root = await mkTmpDir("submodule-like");
59
+ const mid = path.join(root, "mid");
60
+ const leaf = path.join(mid, "leaf");
58
61
  await fs.ensureDir(leaf);
59
62
  // outer repo
60
- await fs.ensureDir(path.join(root, '.git'));
63
+ await fs.ensureDir(path.join(root, ".git"));
61
64
  // inner submodule-like .git file
62
- await fs.writeFile(path.join(mid, '.git'), 'gitdir: ../.git/modules/mid\n');
65
+ await fs.writeFile(path.join(mid, ".git"), "gitdir: ../.git/modules/mid\n");
63
66
  const found = await findProjectRoot(leaf);
64
- assertEqual(found, root, 'outermost .git should win on tie weight');
65
- return { name: 'submodule-like-gitfile', ok: true };
67
+ assertEqual(found, root, "outermost .git should win on tie weight");
68
+ return { name: "submodule-like-gitfile", ok: true };
66
69
  }
67
70
  }
68
71
 
@@ -79,278 +82,325 @@ function assertEqual(actual, expected, msg) {
79
82
  }
80
83
 
81
84
  async function testSentinel() {
82
- const root = await mkTmpDir('sentinel');
83
- const nested = path.join(root, 'a', 'b', 'c');
85
+ const root = await mkTmpDir("sentinel");
86
+ const nested = path.join(root, "a", "b", "c");
84
87
  await fs.ensureDir(nested);
85
- await fs.writeFile(path.join(root, '.project-root'), '\n');
88
+ await fs.writeFile(path.join(root, ".project-root"), "\n");
86
89
  const found = await findProjectRoot(nested);
87
- await assertEqual(found, root, 'sentinel .project-root should win');
88
- return { name: 'sentinel', ok: true };
90
+ await assertEqual(found, root, "sentinel .project-root should win");
91
+ return { name: "sentinel", ok: true };
89
92
  }
90
93
 
91
94
  async function testOtherSentinels() {
92
- const root = await mkTmpDir('other-sentinels');
93
- const nested = path.join(root, 'x', 'y');
95
+ const root = await mkTmpDir("other-sentinels");
96
+ const nested = path.join(root, "x", "y");
94
97
  await fs.ensureDir(nested);
95
- await fs.writeFile(path.join(root, '.workspace-root'), '\n');
98
+ await fs.writeFile(path.join(root, ".workspace-root"), "\n");
96
99
  const found1 = await findProjectRoot(nested);
97
- assertEqual(found1, root, 'sentinel .workspace-root should win');
100
+ assertEqual(found1, root, "sentinel .workspace-root should win");
98
101
 
99
- await fs.remove(path.join(root, '.workspace-root'));
100
- await fs.writeFile(path.join(root, '.repo-root'), '\n');
102
+ await fs.remove(path.join(root, ".workspace-root"));
103
+ await fs.writeFile(path.join(root, ".repo-root"), "\n");
101
104
  const found2 = await findProjectRoot(nested);
102
- assertEqual(found2, root, 'sentinel .repo-root should win');
103
- return { name: 'other-sentinels', ok: true };
105
+ assertEqual(found2, root, "sentinel .repo-root should win");
106
+ return { name: "other-sentinels", ok: true };
104
107
  }
105
108
 
106
109
  async function testGitCliAndMarker() {
107
- const hasGit = await cmdAvailable('git');
108
- if (!hasGit) return { name: 'git-cli', ok: true, skipped: true };
110
+ const hasGit = await cmdAvailable("git");
111
+ if (!hasGit) return { name: "git-cli", ok: true, skipped: true };
109
112
 
110
- const root = await mkTmpDir('git');
111
- const nested = path.join(root, 'pkg', 'src');
113
+ const root = await mkTmpDir("git");
114
+ const nested = path.join(root, "pkg", "src");
112
115
  await fs.ensureDir(nested);
113
- await execFileAsync('git', ['init'], { cwd: root, timeout: 2000 });
116
+ await execFileAsync("git", ["init"], { cwd: root, timeout: 2000 });
114
117
  const found = await findProjectRoot(nested);
115
- await assertEqual(found, root, 'git toplevel should be detected');
116
- return { name: 'git-cli', ok: true };
118
+ await assertEqual(found, root, "git toplevel should be detected");
119
+ return { name: "git-cli", ok: true };
117
120
  }
118
121
 
119
122
  async function testHgMarkerOrCli() {
120
123
  // Prefer simple marker test to avoid requiring Mercurial install
121
- const root = await mkTmpDir('hg');
122
- const nested = path.join(root, 'lib');
124
+ const root = await mkTmpDir("hg");
125
+ const nested = path.join(root, "lib");
123
126
  await fs.ensureDir(nested);
124
- await fs.ensureDir(path.join(root, '.hg'));
127
+ await fs.ensureDir(path.join(root, ".hg"));
125
128
  const found = await findProjectRoot(nested);
126
- await assertEqual(found, root, '.hg marker should be detected');
127
- return { name: 'hg-marker', ok: true };
129
+ await assertEqual(found, root, ".hg marker should be detected");
130
+ return { name: "hg-marker", ok: true };
128
131
  }
129
132
 
130
133
  async function testWorkspacePnpm() {
131
- const root = await mkTmpDir('pnpm-workspace');
132
- const pkgA = path.join(root, 'packages', 'a');
134
+ const root = await mkTmpDir("pnpm-workspace");
135
+ const pkgA = path.join(root, "packages", "a");
133
136
  await fs.ensureDir(pkgA);
134
- await fs.writeFile(path.join(root, 'pnpm-workspace.yaml'), 'packages:\n - packages/*\n');
137
+ await fs.writeFile(
138
+ path.join(root, "pnpm-workspace.yaml"),
139
+ "packages:\n - packages/*\n",
140
+ );
135
141
  const found = await findProjectRoot(pkgA);
136
- await assertEqual(found, root, 'pnpm-workspace.yaml should be detected');
137
- return { name: 'pnpm-workspace', ok: true };
142
+ await assertEqual(found, root, "pnpm-workspace.yaml should be detected");
143
+ return { name: "pnpm-workspace", ok: true };
138
144
  }
139
145
 
140
146
  async function testPackageJsonWorkspaces() {
141
- const root = await mkTmpDir('package-workspaces');
142
- const pkgA = path.join(root, 'packages', 'a');
147
+ const root = await mkTmpDir("package-workspaces");
148
+ const pkgA = path.join(root, "packages", "a");
143
149
  await fs.ensureDir(pkgA);
144
150
  await fs.writeJson(
145
- path.join(root, 'package.json'),
146
- { private: true, workspaces: ['packages/*'] },
151
+ path.join(root, "package.json"),
152
+ { private: true, workspaces: ["packages/*"] },
147
153
  { spaces: 2 },
148
154
  );
149
155
  const found = await findProjectRoot(pkgA);
150
- await assertEqual(found, root, 'package.json workspaces should be detected');
151
- return { name: 'package.json-workspaces', ok: true };
156
+ await assertEqual(found, root, "package.json workspaces should be detected");
157
+ return { name: "package.json-workspaces", ok: true };
152
158
  }
153
159
 
154
160
  async function testLockfiles() {
155
- const root = await mkTmpDir('lockfiles');
156
- const nested = path.join(root, 'src');
161
+ const root = await mkTmpDir("lockfiles");
162
+ const nested = path.join(root, "src");
157
163
  await fs.ensureDir(nested);
158
- await fs.writeFile(path.join(root, 'yarn.lock'), '\n');
164
+ await fs.writeFile(path.join(root, "yarn.lock"), "\n");
159
165
  const found = await findProjectRoot(nested);
160
- await assertEqual(found, root, 'yarn.lock should be detected');
161
- return { name: 'lockfiles', ok: true };
166
+ await assertEqual(found, root, "yarn.lock should be detected");
167
+ return { name: "lockfiles", ok: true };
162
168
  }
163
169
 
164
170
  async function testLanguageConfigs() {
165
- const root = await mkTmpDir('lang-configs');
166
- const nested = path.join(root, 'x', 'y');
171
+ const root = await mkTmpDir("lang-configs");
172
+ const nested = path.join(root, "x", "y");
167
173
  await fs.ensureDir(nested);
168
- await fs.writeFile(path.join(root, 'pyproject.toml'), "[tool.poetry]\nname='tmp'\n");
174
+ await fs.writeFile(
175
+ path.join(root, "pyproject.toml"),
176
+ "[tool.poetry]\nname='tmp'\n",
177
+ );
169
178
  const found = await findProjectRoot(nested);
170
- await assertEqual(found, root, 'pyproject.toml should be detected');
171
- return { name: 'language-configs', ok: true };
179
+ await assertEqual(found, root, "pyproject.toml should be detected");
180
+ return { name: "language-configs", ok: true };
172
181
  }
173
182
 
174
183
  async function testPreferOuterOnTie() {
175
- const root = await mkTmpDir('tie');
176
- const mid = path.join(root, 'mid');
177
- const leaf = path.join(mid, 'leaf');
184
+ const root = await mkTmpDir("tie");
185
+ const mid = path.join(root, "mid");
186
+ const leaf = path.join(mid, "leaf");
178
187
  await fs.ensureDir(leaf);
179
188
  // same weight marker at two levels
180
- await fs.writeFile(path.join(root, 'requirements.txt'), '\n');
181
- await fs.writeFile(path.join(mid, 'requirements.txt'), '\n');
189
+ await fs.writeFile(path.join(root, "requirements.txt"), "\n");
190
+ await fs.writeFile(path.join(mid, "requirements.txt"), "\n");
182
191
  const found = await findProjectRoot(leaf);
183
- await assertEqual(found, root, 'outermost directory should win on equal weight');
184
- return { name: 'prefer-outermost-tie', ok: true };
192
+ await assertEqual(
193
+ found,
194
+ root,
195
+ "outermost directory should win on equal weight",
196
+ );
197
+ return { name: "prefer-outermost-tie", ok: true };
185
198
  }
186
199
 
187
200
  // Additional coverage: Bazel, Nx/Turbo/Rush, Go workspaces, Deno, Java/Scala, PHP, Rust, Nix, Changesets, env markers,
188
201
  // and priority interaction between package.json and lockfiles.
189
202
 
190
203
  async function testBazelWorkspace() {
191
- const root = await mkTmpDir('bazel');
192
- const nested = path.join(root, 'apps', 'svc');
204
+ const root = await mkTmpDir("bazel");
205
+ const nested = path.join(root, "apps", "svc");
193
206
  await fs.ensureDir(nested);
194
- await fs.writeFile(path.join(root, 'WORKSPACE'), 'workspace(name="tmp")\n');
207
+ await fs.writeFile(path.join(root, "WORKSPACE"), 'workspace(name="tmp")\n');
195
208
  const found = await findProjectRoot(nested);
196
- await assertEqual(found, root, 'Bazel WORKSPACE should be detected');
197
- return { name: 'bazel-workspace', ok: true };
209
+ await assertEqual(found, root, "Bazel WORKSPACE should be detected");
210
+ return { name: "bazel-workspace", ok: true };
198
211
  }
199
212
 
200
213
  async function testNx() {
201
- const root = await mkTmpDir('nx');
202
- const nested = path.join(root, 'apps', 'web');
214
+ const root = await mkTmpDir("nx");
215
+ const nested = path.join(root, "apps", "web");
203
216
  await fs.ensureDir(nested);
204
- await fs.writeJson(path.join(root, 'nx.json'), { npmScope: 'tmp' }, { spaces: 2 });
217
+ await fs.writeJson(
218
+ path.join(root, "nx.json"),
219
+ { npmScope: "tmp" },
220
+ { spaces: 2 },
221
+ );
205
222
  const found = await findProjectRoot(nested);
206
- await assertEqual(found, root, 'nx.json should be detected');
207
- return { name: 'nx', ok: true };
223
+ await assertEqual(found, root, "nx.json should be detected");
224
+ return { name: "nx", ok: true };
208
225
  }
209
226
 
210
227
  async function testTurbo() {
211
- const root = await mkTmpDir('turbo');
212
- const nested = path.join(root, 'packages', 'x');
228
+ const root = await mkTmpDir("turbo");
229
+ const nested = path.join(root, "packages", "x");
213
230
  await fs.ensureDir(nested);
214
- await fs.writeJson(path.join(root, 'turbo.json'), { pipeline: {} }, { spaces: 2 });
231
+ await fs.writeJson(
232
+ path.join(root, "turbo.json"),
233
+ { pipeline: {} },
234
+ { spaces: 2 },
235
+ );
215
236
  const found = await findProjectRoot(nested);
216
- await assertEqual(found, root, 'turbo.json should be detected');
217
- return { name: 'turbo', ok: true };
237
+ await assertEqual(found, root, "turbo.json should be detected");
238
+ return { name: "turbo", ok: true };
218
239
  }
219
240
 
220
241
  async function testRush() {
221
- const root = await mkTmpDir('rush');
222
- const nested = path.join(root, 'apps', 'a');
242
+ const root = await mkTmpDir("rush");
243
+ const nested = path.join(root, "apps", "a");
223
244
  await fs.ensureDir(nested);
224
- await fs.writeJson(path.join(root, 'rush.json'), { projectFolderMinDepth: 1 }, { spaces: 2 });
245
+ await fs.writeJson(
246
+ path.join(root, "rush.json"),
247
+ { projectFolderMinDepth: 1 },
248
+ { spaces: 2 },
249
+ );
225
250
  const found = await findProjectRoot(nested);
226
- await assertEqual(found, root, 'rush.json should be detected');
227
- return { name: 'rush', ok: true };
251
+ await assertEqual(found, root, "rush.json should be detected");
252
+ return { name: "rush", ok: true };
228
253
  }
229
254
 
230
255
  async function testGoWorkAndMod() {
231
- const root = await mkTmpDir('gowork');
232
- const mod = path.join(root, 'modA');
233
- const nested = path.join(mod, 'pkg');
256
+ const root = await mkTmpDir("gowork");
257
+ const mod = path.join(root, "modA");
258
+ const nested = path.join(mod, "pkg");
234
259
  await fs.ensureDir(nested);
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');
260
+ await fs.writeFile(path.join(root, "go.work"), "go 1.22\nuse ./modA\n");
261
+ await fs.writeFile(
262
+ path.join(mod, "go.mod"),
263
+ "module example.com/a\ngo 1.22\n",
264
+ );
237
265
  const found = await findProjectRoot(nested);
238
- await assertEqual(found, root, 'go.work should define the workspace root');
239
- return { name: 'go-work', ok: true };
266
+ await assertEqual(found, root, "go.work should define the workspace root");
267
+ return { name: "go-work", ok: true };
240
268
  }
241
269
 
242
270
  async function testDenoJson() {
243
- const root = await mkTmpDir('deno');
244
- const nested = path.join(root, 'src');
271
+ const root = await mkTmpDir("deno");
272
+ const nested = path.join(root, "src");
245
273
  await fs.ensureDir(nested);
246
- await fs.writeJson(path.join(root, 'deno.json'), { tasks: {} }, { spaces: 2 });
274
+ await fs.writeJson(
275
+ path.join(root, "deno.json"),
276
+ { tasks: {} },
277
+ { spaces: 2 },
278
+ );
247
279
  const found = await findProjectRoot(nested);
248
- await assertEqual(found, root, 'deno.json should be detected');
249
- return { name: 'deno-json', ok: true };
280
+ await assertEqual(found, root, "deno.json should be detected");
281
+ return { name: "deno-json", ok: true };
250
282
  }
251
283
 
252
284
  async function testGradleSettings() {
253
- const root = await mkTmpDir('gradle');
254
- const nested = path.join(root, 'app');
285
+ const root = await mkTmpDir("gradle");
286
+ const nested = path.join(root, "app");
255
287
  await fs.ensureDir(nested);
256
- await fs.writeFile(path.join(root, 'settings.gradle'), "rootProject.name='tmp'\n");
288
+ await fs.writeFile(
289
+ path.join(root, "settings.gradle"),
290
+ "rootProject.name='tmp'\n",
291
+ );
257
292
  const found = await findProjectRoot(nested);
258
- await assertEqual(found, root, 'settings.gradle should be detected');
259
- return { name: 'gradle-settings', ok: true };
293
+ await assertEqual(found, root, "settings.gradle should be detected");
294
+ return { name: "gradle-settings", ok: true };
260
295
  }
261
296
 
262
297
  async function testMavenPom() {
263
- const root = await mkTmpDir('maven');
264
- const nested = path.join(root, 'module');
298
+ const root = await mkTmpDir("maven");
299
+ const nested = path.join(root, "module");
265
300
  await fs.ensureDir(nested);
266
- await fs.writeFile(path.join(root, 'pom.xml'), '<project></project>\n');
301
+ await fs.writeFile(path.join(root, "pom.xml"), "<project></project>\n");
267
302
  const found = await findProjectRoot(nested);
268
- await assertEqual(found, root, 'pom.xml should be detected');
269
- return { name: 'maven-pom', ok: true };
303
+ await assertEqual(found, root, "pom.xml should be detected");
304
+ return { name: "maven-pom", ok: true };
270
305
  }
271
306
 
272
307
  async function testSbtBuild() {
273
- const root = await mkTmpDir('sbt');
274
- const nested = path.join(root, 'sub');
308
+ const root = await mkTmpDir("sbt");
309
+ const nested = path.join(root, "sub");
275
310
  await fs.ensureDir(nested);
276
- await fs.writeFile(path.join(root, 'build.sbt'), 'name := "tmp"\n');
311
+ await fs.writeFile(path.join(root, "build.sbt"), 'name := "tmp"\n');
277
312
  const found = await findProjectRoot(nested);
278
- await assertEqual(found, root, 'build.sbt should be detected');
279
- return { name: 'sbt-build', ok: true };
313
+ await assertEqual(found, root, "build.sbt should be detected");
314
+ return { name: "sbt-build", ok: true };
280
315
  }
281
316
 
282
317
  async function testComposer() {
283
- const root = await mkTmpDir('composer');
284
- const nested = path.join(root, 'src');
318
+ const root = await mkTmpDir("composer");
319
+ const nested = path.join(root, "src");
285
320
  await fs.ensureDir(nested);
286
- await fs.writeJson(path.join(root, 'composer.json'), { name: 'tmp/pkg' }, { spaces: 2 });
287
- await fs.writeFile(path.join(root, 'composer.lock'), '{}\n');
321
+ await fs.writeJson(
322
+ path.join(root, "composer.json"),
323
+ { name: "tmp/pkg" },
324
+ { spaces: 2 },
325
+ );
326
+ await fs.writeFile(path.join(root, "composer.lock"), "{}\n");
288
327
  const found = await findProjectRoot(nested);
289
- await assertEqual(found, root, 'composer.{json,lock} should be detected');
290
- return { name: 'composer', ok: true };
328
+ await assertEqual(found, root, "composer.{json,lock} should be detected");
329
+ return { name: "composer", ok: true };
291
330
  }
292
331
 
293
332
  async function testCargo() {
294
- const root = await mkTmpDir('cargo');
295
- const nested = path.join(root, 'src');
333
+ const root = await mkTmpDir("cargo");
334
+ const nested = path.join(root, "src");
296
335
  await fs.ensureDir(nested);
297
- await fs.writeFile(path.join(root, 'Cargo.toml'), "[package]\nname='tmp'\nversion='0.0.0'\n");
336
+ await fs.writeFile(
337
+ path.join(root, "Cargo.toml"),
338
+ "[package]\nname='tmp'\nversion='0.0.0'\n",
339
+ );
298
340
  const found = await findProjectRoot(nested);
299
- await assertEqual(found, root, 'Cargo.toml should be detected');
300
- return { name: 'cargo', ok: true };
341
+ await assertEqual(found, root, "Cargo.toml should be detected");
342
+ return { name: "cargo", ok: true };
301
343
  }
302
344
 
303
345
  async function testNixFlake() {
304
- const root = await mkTmpDir('nix');
305
- const nested = path.join(root, 'work');
346
+ const root = await mkTmpDir("nix");
347
+ const nested = path.join(root, "work");
306
348
  await fs.ensureDir(nested);
307
- await fs.writeFile(path.join(root, 'flake.nix'), '{ }\n');
349
+ await fs.writeFile(path.join(root, "flake.nix"), "{ }\n");
308
350
  const found = await findProjectRoot(nested);
309
- await assertEqual(found, root, 'flake.nix should be detected');
310
- return { name: 'nix-flake', ok: true };
351
+ await assertEqual(found, root, "flake.nix should be detected");
352
+ return { name: "nix-flake", ok: true };
311
353
  }
312
354
 
313
355
  async function testChangesetConfig() {
314
- const root = await mkTmpDir('changeset');
315
- const nested = path.join(root, 'pkg');
356
+ const root = await mkTmpDir("changeset");
357
+ const nested = path.join(root, "pkg");
316
358
  await fs.ensureDir(nested);
317
- await fs.ensureDir(path.join(root, '.changeset'));
359
+ await fs.ensureDir(path.join(root, ".changeset"));
318
360
  await fs.writeJson(
319
- path.join(root, '.changeset', 'config.json'),
320
- { $schema: 'https://unpkg.com/@changesets/config@2.3.1/schema.json' },
361
+ path.join(root, ".changeset", "config.json"),
362
+ { $schema: "https://unpkg.com/@changesets/config@2.3.1/schema.json" },
321
363
  { spaces: 2 },
322
364
  );
323
365
  const found = await findProjectRoot(nested);
324
- await assertEqual(found, root, '.changeset/config.json should be detected');
325
- return { name: 'changesets', ok: true };
366
+ await assertEqual(found, root, ".changeset/config.json should be detected");
367
+ return { name: "changesets", ok: true };
326
368
  }
327
369
 
328
370
  async function testEnvCustomMarker() {
329
- const root = await mkTmpDir('env-marker');
330
- const nested = path.join(root, 'dir');
371
+ const root = await mkTmpDir("env-marker");
372
+ const nested = path.join(root, "dir");
331
373
  await fs.ensureDir(nested);
332
- await fs.writeFile(path.join(root, 'MY_ROOT'), '\n');
374
+ await fs.writeFile(path.join(root, "MY_ROOT"), "\n");
333
375
  const prev = process.env.PROJECT_ROOT_MARKERS;
334
- process.env.PROJECT_ROOT_MARKERS = 'MY_ROOT';
376
+ process.env.PROJECT_ROOT_MARKERS = "MY_ROOT";
335
377
  try {
336
378
  const found = await findProjectRoot(nested);
337
- await assertEqual(found, root, 'custom env marker should be honored');
379
+ await assertEqual(found, root, "custom env marker should be honored");
338
380
  } finally {
339
381
  if (prev === undefined) delete process.env.PROJECT_ROOT_MARKERS;
340
382
  else process.env.PROJECT_ROOT_MARKERS = prev;
341
383
  }
342
- return { name: 'env-custom-marker', ok: true };
384
+ return { name: "env-custom-marker", ok: true };
343
385
  }
344
386
 
345
387
  async function testPackageLowPriorityVsLock() {
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 };
388
+ const root = await mkTmpDir("pkg-vs-lock");
389
+ const nested = path.join(root, "nested");
390
+ await fs.ensureDir(path.join(nested, "deep"));
391
+ await fs.writeJson(
392
+ path.join(nested, "package.json"),
393
+ { name: "nested" },
394
+ { spaces: 2 },
395
+ );
396
+ await fs.writeFile(path.join(root, "yarn.lock"), "\n");
397
+ const found = await findProjectRoot(path.join(nested, "deep"));
398
+ await assertEqual(
399
+ found,
400
+ root,
401
+ "lockfile at root should outrank nested package.json",
402
+ );
403
+ return { name: "package-vs-lock-priority", ok: true };
354
404
  }
355
405
 
356
406
  async function run() {
@@ -389,17 +439,22 @@ async function run() {
389
439
  try {
390
440
  const r = await t();
391
441
  results.push({ ...r, ok: true });
392
- console.log(`✔ ${r.name}${r.skipped ? ' (skipped)' : ''}`);
442
+ console.log(`✔ ${r.name}${r.skipped ? " (skipped)" : ""}`);
393
443
  } catch (error) {
394
- console.error(`✖ ${t.name}:`, error && error.message ? error.message : error);
444
+ console.error(
445
+ `✖ ${t.name}:`,
446
+ error && error.message ? error.message : error,
447
+ );
395
448
  results.push({ name: t.name, ok: false, error: String(error) });
396
449
  }
397
450
  }
398
451
 
399
452
  const failed = results.filter((r) => !r.ok);
400
- console.log('\nSummary:');
453
+ console.log("\nSummary:");
401
454
  for (const r of results) {
402
- console.log(`- ${r.name}: ${r.ok ? 'ok' : 'FAIL'}${r.skipped ? ' (skipped)' : ''}`);
455
+ console.log(
456
+ `- ${r.name}: ${r.ok ? "ok" : "FAIL"}${r.skipped ? " (skipped)" : ""}`,
457
+ );
403
458
  }
404
459
 
405
460
  if (failed.length > 0) {
@@ -408,6 +463,6 @@ async function run() {
408
463
  }
409
464
 
410
465
  run().catch((error) => {
411
- console.error('Fatal error:', error);
466
+ console.error("Fatal error:", error);
412
467
  process.exit(1);
413
468
  });