opencode-swarm 7.184.10 → 7.184.12

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 (101) hide show
  1. package/README.md +26 -24
  2. package/binaries/win32-arm64/swarm-sandbox-runner.exe +0 -0
  3. package/binaries/win32-x64/swarm-sandbox-runner.exe +0 -0
  4. package/dist/background/delegation-lifecycle.d.ts +6 -2
  5. package/dist/background/pending-delegations.d.ts +23 -1
  6. package/dist/cli/auto-checkpoint-7y3x5cv2.js +183 -0
  7. package/dist/cli/checkpoint-j39f718z.js +51 -0
  8. package/dist/cli/{coder-settlement-b47vmcm9.js → coder-settlement-12ytzqvs.js} +13 -11
  9. package/dist/cli/{config-doctor-3j3m2ccc.js → config-doctor-vphp4shw.js} +2 -2
  10. package/dist/cli/{core-td5kj2w8.js → core-5a1awn37.js} +3 -3
  11. package/dist/cli/{curation-policy-xe77n9zj.js → curation-policy-1rzheh1x.js} +6 -5
  12. package/dist/cli/{curator-drift-wxyhgz7y.js → curator-drift-ex0qxt9p.js} +9 -9
  13. package/dist/cli/curator-llm-factory-ktkat73z.js +81 -0
  14. package/dist/cli/{curator-c7we2apv.js → curator-x2hxjdac.js} +48 -42
  15. package/dist/cli/{dispatch-cv89p2y1.js → dispatch-0ejzwb45.js} +2 -1
  16. package/dist/cli/{evidence-summary-service-2t1ttdgj.js → evidence-summary-service-pfksvp16.js} +24 -19
  17. package/dist/cli/{gate-evidence-5w2q1h5w.js → gate-evidence-8n8kb8tb.js} +8 -6
  18. package/dist/cli/guardrail-explain-px94swcz.js +82 -0
  19. package/dist/cli/{guardrail-log-5zwqdhaf.js → guardrail-log-xevw9xht.js} +7 -7
  20. package/dist/cli/{guardrail-reset-qnq1swzf.js → guardrail-reset-18wvqbga.js} +48 -42
  21. package/dist/cli/hive-promoter-5r35f3ap.js +96 -0
  22. package/dist/cli/{index-260174fq.js → index-27wzbb3n.js} +4 -4
  23. package/dist/cli/{index-b6saxkqj.js → index-2cbt4b7t.js} +2 -2
  24. package/dist/cli/{index-tzyppdv7.js → index-2p1an48j.js} +3 -3
  25. package/dist/cli/index-2y9b45jj.js +24 -0
  26. package/dist/cli/index-34ysgky0.js +954 -0
  27. package/dist/cli/{index-1g2bm6ht.js → index-39ng38sp.js} +1 -1
  28. package/dist/cli/{index-hk0eh9qb.js → index-4g4sr4ah.js} +1 -1
  29. package/dist/cli/{index-cjh92zm9.js → index-53fy0aq2.js} +52 -46
  30. package/dist/cli/{index-7849gy4s.js → index-5w404j07.js} +8 -6
  31. package/dist/cli/index-5y1qxw0x.js +963 -0
  32. package/dist/cli/{index-35cdceqf.js → index-6cjpmvpa.js} +2 -20
  33. package/dist/cli/{index-yyvkeev1.js → index-74e43s33.js} +4 -4
  34. package/dist/cli/index-7bxssqaf.js +2676 -0
  35. package/dist/cli/index-7pmbnjvj.js +11 -0
  36. package/dist/cli/{index-j4efx148.js → index-7r3pkgma.js} +5 -5
  37. package/dist/cli/{index-kg2mb1m5.js → index-7rzgdcvc.js} +2 -2
  38. package/dist/cli/{index-hamqvzqn.js → index-9nyqt3kb.js} +10 -2682
  39. package/dist/cli/{index-hs26e3k6.js → index-a53f5t61.js} +2 -2
  40. package/dist/cli/{index-ta2kp4xy.js → index-azyhws0s.js} +9 -9
  41. package/dist/cli/{index-0mj7hc2z.js → index-bntwj2tp.js} +5 -5
  42. package/dist/cli/{index-aae38s4x.js → index-cftva3pa.js} +2 -11
  43. package/dist/cli/{index-8n2dh26k.js → index-cvam011t.js} +1 -1
  44. package/dist/cli/{index-3rfc2v1d.js → index-d42kjvwy.js} +2 -2
  45. package/dist/cli/{index-gnryhmak.js → index-dftztz4y.js} +1 -1
  46. package/dist/cli/{index-45h58v8d.js → index-dze05t9h.js} +7 -7
  47. package/dist/cli/{index-54f1payv.js → index-eqfmabtd.js} +2 -2
  48. package/dist/cli/{index-4szb7qpv.js → index-fbp7dqfe.js} +10 -38
  49. package/dist/cli/{index-0ajyhjht.js → index-fxf3zhtq.js} +1 -1
  50. package/dist/cli/index-g4fs6z4e.js +37 -0
  51. package/dist/cli/{index-bt1k1ek3.js → index-hfs97j0c.js} +1 -1
  52. package/dist/cli/{index-nh5fxxc9.js → index-jtg0zsnx.js} +4 -2
  53. package/dist/cli/{index-9jz73x8x.js → index-kcbkce43.js} +48 -42
  54. package/dist/cli/{index-07fqfseh.js → index-m1pqenq9.js} +2 -2
  55. package/dist/cli/{index-be5h97z8.js → index-mfqge140.js} +1 -1
  56. package/dist/cli/{index-3wgvpxmv.js → index-n572fdtf.js} +2969 -3838
  57. package/dist/cli/{index-5m1b70z3.js → index-pwcbtbhd.js} +4 -4
  58. package/dist/cli/{index-95vytkv6.js → index-rbvndg2d.js} +3 -3
  59. package/dist/cli/{index-thcyqqdq.js → index-ryafwy2b.js} +1 -1
  60. package/dist/cli/{index-0d7e94zg.js → index-s7rap90y.js} +7 -7
  61. package/dist/cli/{index-q7q00ppx.js → index-tyzmjw2s.js} +7 -7
  62. package/dist/cli/{index-bhj3zx43.js → index-vk49btd6.js} +1 -1
  63. package/dist/cli/{index-fn1y9jpv.js → index-w33zgxn6.js} +3 -3
  64. package/dist/cli/{index-x1gyqgze.js → index-w6w6ny06.js} +2 -2
  65. package/dist/cli/{index-f336fv53.js → index-x0jybm74.js} +3 -3
  66. package/dist/cli/{index-pj877ads.js → index-ymhs7hng.js} +5 -5
  67. package/dist/cli/{index-19e61hgv.js → index-yvmvsej2.js} +55 -971
  68. package/dist/cli/{index-x119gdak.js → index-yx5z4200.js} +2 -2
  69. package/dist/cli/{index-dqpq8m9w.js → index-yydv4jfy.js} +2 -2
  70. package/dist/cli/{index-fxtgxe04.js → index-zmtpnyff.js} +8 -8
  71. package/dist/cli/{index-djnxehe7.js → index-zzebc1td.js} +44 -16
  72. package/dist/cli/index.js +49 -43
  73. package/dist/cli/{knowledge-escalator-912nwxb4.js → knowledge-escalator-z5qb6y6s.js} +13 -12
  74. package/dist/cli/{knowledge-events-hvzj872f.js → knowledge-events-gv173j4d.js} +11 -10
  75. package/dist/cli/{knowledge-link-8tetyje0.js → knowledge-link-vvyfhhmr.js} +5 -4
  76. package/dist/cli/{knowledge-store-sqx0axb6.js → knowledge-store-g4sjhep5.js} +6 -5
  77. package/dist/cli/{knowledge-validator-d8m7p2vg.js → knowledge-validator-8whkrsrb.js} +8 -7
  78. package/dist/cli/{mcp-nxxy0dq0.js → mcp-x25b3q1g.js} +1 -1
  79. package/dist/cli/{model-preflight-2mmep72y.js → model-preflight-b21njmda.js} +48 -42
  80. package/dist/cli/{pending-delegations-fat9r7w1.js → pending-delegations-f6rmrqzn.js} +16 -12
  81. package/dist/cli/{pr-subscriptions-8xv7gaeb.js → pr-subscriptions-nmbme4kp.js} +14 -12
  82. package/dist/cli/{pr-workflow-gate-g9exsspv.js → pr-workflow-gate-mtzfsmkg.js} +48 -42
  83. package/dist/cli/{runner-ff0bj9w0.js → runner-cwmgb9rw.js} +6 -5
  84. package/dist/cli/{scan-cursor-h9tza4z6.js → scan-cursor-gmpjsr3a.js} +7 -6
  85. package/dist/cli/{schema-1p9cw863.js → schema-rvxnxp4n.js} +1 -1
  86. package/dist/cli/{scope-persistence-xv6b7daq.js → scope-persistence-fqenprq7.js} +17 -15
  87. package/dist/cli/{server-z5401cje.js → server-ws1my2dz.js} +62 -54
  88. package/dist/cli/{skill-generator-72n07p92.js → skill-generator-pt1epeer.js} +15 -14
  89. package/dist/cli/{snapshot-coordination-init-xjyv8kxx.js → snapshot-coordination-init-9zrktnh5.js} +48 -42
  90. package/dist/cli/{speckit-checkoff-828xrjfd.js → speckit-checkoff-k13zhgh5.js} +4 -4
  91. package/dist/cli/{telemetry-39zmx4fp.js → telemetry-n6y2sjhg.js} +1 -1
  92. package/dist/cli/{workspace-snapshot-nrbvjh1c.js → workspace-snapshot-gnwak61t.js} +3 -3
  93. package/dist/cli/{worktree-collision-ownership-cr6vv3xk.js → worktree-collision-ownership-mw3rkc4v.js} +18 -16
  94. package/dist/cli/{worktree-isolation-6yycqkc6.js → worktree-isolation-0rtrpf0b.js} +48 -42
  95. package/dist/index.js +541 -541
  96. package/dist/plan/auto-checkpoint.d.ts +56 -0
  97. package/dist/plan/manager.d.ts +8 -12
  98. package/package.json +1 -1
  99. package/dist/cli/curator-llm-factory-1260n9ty.js +0 -75
  100. package/dist/cli/guardrail-explain-qeywnmza.js +0 -76
  101. package/dist/cli/hive-promoter-c3c6tz3b.js +0 -90
@@ -0,0 +1,2676 @@
1
+ // @bun
2
+ import {
3
+ tool
4
+ } from "./index-9nyqt3kb.js";
5
+ import {
6
+ bunSpawnSync
7
+ } from "./index-2s7dvzff.js";
8
+ import {
9
+ simpleGlobToRegex
10
+ } from "./index-c1ggtmpm.js";
11
+ import {
12
+ warn
13
+ } from "./index-zgwm4ryv.js";
14
+
15
+ // src/lang/detector.ts
16
+ import { access, readdir } from "fs/promises";
17
+ import { extname, join } from "path";
18
+
19
+ // src/lang/profiles.ts
20
+ class LanguageRegistry {
21
+ profiles;
22
+ extensionIndex;
23
+ constructor() {
24
+ this.profiles = new Map;
25
+ this.extensionIndex = new Map;
26
+ }
27
+ unregister(id) {
28
+ const profile = this.profiles.get(id);
29
+ if (!profile)
30
+ return;
31
+ this.profiles.delete(id);
32
+ if (!profile.parserOnly) {
33
+ for (const ext of profile.extensions) {
34
+ if (this.extensionIndex.get(ext) === id) {
35
+ this.extensionIndex.delete(ext);
36
+ }
37
+ }
38
+ }
39
+ }
40
+ register(profile) {
41
+ const existing = this.profiles.get(profile.id);
42
+ if (existing && existing !== profile) {
43
+ throw new Error(`LanguageRegistry: profile id "${profile.id}" registered twice. ` + `Each LanguageProfile.id must be unique. ` + `Got: ${profile.displayName} vs existing ${existing.displayName}.`);
44
+ }
45
+ if (!profile.parserOnly) {
46
+ for (const ext of profile.extensions) {
47
+ const claimedBy = this.extensionIndex.get(ext);
48
+ if (claimedBy && claimedBy !== profile.id) {
49
+ throw new Error(`LanguageRegistry: extension "${ext}" registered by both ` + `"${claimedBy}" and "${profile.id}". A non-parserOnly profile ` + `must not collide on extensions. If both languages legitimately ` + `share an extension, mark one parserOnly: true.`);
50
+ }
51
+ this.extensionIndex.set(ext, profile.id);
52
+ }
53
+ }
54
+ this.profiles.set(profile.id, profile);
55
+ }
56
+ get(id) {
57
+ return this.profiles.get(id);
58
+ }
59
+ getById(id) {
60
+ return this.profiles.get(id);
61
+ }
62
+ getByExtension(ext) {
63
+ const profileId = this.extensionIndex.get(ext);
64
+ if (profileId) {
65
+ return this.profiles.get(profileId);
66
+ }
67
+ return;
68
+ }
69
+ getAll() {
70
+ return Array.from(this.profiles.values());
71
+ }
72
+ getTier(tier) {
73
+ return Array.from(this.profiles.values()).filter((p) => p.tier === tier);
74
+ }
75
+ }
76
+ var LANGUAGE_REGISTRY = new LanguageRegistry;
77
+ LANGUAGE_REGISTRY.register({
78
+ id: "typescript",
79
+ displayName: "TypeScript",
80
+ tier: 1,
81
+ extensions: [".ts", ".tsx"],
82
+ treeSitter: {
83
+ grammarId: "typescript",
84
+ wasmFile: "tree-sitter-typescript.wasm",
85
+ commentNodes: ["comment", "line_comment", "block_comment"]
86
+ },
87
+ build: {
88
+ detectFiles: ["package.json"],
89
+ commands: [
90
+ {
91
+ name: "bun build",
92
+ cmd: "bun run build",
93
+ detectFile: "package.json",
94
+ priority: 10
95
+ },
96
+ {
97
+ name: "tsc",
98
+ cmd: "tsc --noEmit",
99
+ detectFile: "tsconfig.json",
100
+ priority: 9
101
+ },
102
+ {
103
+ name: "vite build",
104
+ cmd: "vite build",
105
+ detectFile: "vite.config.ts",
106
+ priority: 8
107
+ }
108
+ ]
109
+ },
110
+ test: {
111
+ detectFiles: [
112
+ "package.json",
113
+ "vitest.config.ts",
114
+ "jest.config.js",
115
+ ".mocharc.js",
116
+ ".mocharc.json"
117
+ ],
118
+ frameworks: [
119
+ {
120
+ name: "vitest",
121
+ detect: "vitest.config.ts",
122
+ cmd: "bun test",
123
+ priority: 10
124
+ },
125
+ { name: "jest", detect: "jest.config.js", cmd: "jest", priority: 9 },
126
+ {
127
+ name: "bun:test",
128
+ detect: "bun.lock",
129
+ cmd: "bun test",
130
+ priority: 8
131
+ },
132
+ {
133
+ name: "mocha",
134
+ detect: ".mocharc.json",
135
+ cmd: "mocha",
136
+ priority: 7
137
+ }
138
+ ]
139
+ },
140
+ lint: {
141
+ detectFiles: [
142
+ "biome.json",
143
+ "biome.jsonc",
144
+ ".eslintrc.js",
145
+ ".eslintrc.json"
146
+ ],
147
+ linters: [
148
+ {
149
+ name: "biome",
150
+ detect: "biome.json",
151
+ cmd: "biome check --write .",
152
+ priority: 10
153
+ },
154
+ {
155
+ name: "eslint",
156
+ detect: ".eslintrc.js",
157
+ cmd: "eslint --fix .",
158
+ priority: 9
159
+ }
160
+ ]
161
+ },
162
+ audit: {
163
+ detectFiles: ["package.json"],
164
+ command: "npm audit --json",
165
+ outputFormat: "json"
166
+ },
167
+ sast: { nativeRuleSet: "javascript", semgrepSupport: "ga" },
168
+ prompts: {
169
+ coderConstraints: [
170
+ "Use strict TypeScript; no implicit any or type assertions without justification",
171
+ "Prefer async/await over raw Promises; always handle rejections",
172
+ "Use const/let, never var; prefer immutable data structures",
173
+ "Follow existing import style (ESM); no require() in .ts files",
174
+ "Add JSDoc for all exported functions and types"
175
+ ],
176
+ reviewerChecklist: [
177
+ "Verify no implicit any or unsafe type casts",
178
+ "Check async error handling \u2014 unhandled Promises are bugs",
179
+ "Confirm ESM import consistency (no mixed require/import)",
180
+ "Validate exported API surface matches declared types",
181
+ "Check for missing null/undefined guards on optional fields"
182
+ ]
183
+ }
184
+ });
185
+ LANGUAGE_REGISTRY.register({
186
+ id: "javascript",
187
+ displayName: "JavaScript",
188
+ tier: 1,
189
+ extensions: [".js", ".jsx", ".mjs", ".cjs"],
190
+ treeSitter: {
191
+ grammarId: "javascript",
192
+ wasmFile: "tree-sitter-javascript.wasm",
193
+ commentNodes: ["comment", "line_comment", "block_comment"]
194
+ },
195
+ build: {
196
+ detectFiles: ["package.json"],
197
+ commands: [
198
+ {
199
+ name: "bun build",
200
+ cmd: "bun run build",
201
+ detectFile: "package.json",
202
+ priority: 10
203
+ },
204
+ {
205
+ name: "tsc",
206
+ cmd: "tsc --noEmit",
207
+ detectFile: "tsconfig.json",
208
+ priority: 9
209
+ },
210
+ {
211
+ name: "vite build",
212
+ cmd: "vite build",
213
+ detectFile: "vite.config.ts",
214
+ priority: 8
215
+ }
216
+ ]
217
+ },
218
+ test: {
219
+ detectFiles: [
220
+ "package.json",
221
+ "vitest.config.ts",
222
+ "jest.config.js",
223
+ ".mocharc.js",
224
+ ".mocharc.json"
225
+ ],
226
+ frameworks: [
227
+ {
228
+ name: "vitest",
229
+ detect: "vitest.config.ts",
230
+ cmd: "bun test",
231
+ priority: 10
232
+ },
233
+ { name: "jest", detect: "jest.config.js", cmd: "jest", priority: 9 },
234
+ {
235
+ name: "bun:test",
236
+ detect: "bun.lock",
237
+ cmd: "bun test",
238
+ priority: 8
239
+ },
240
+ {
241
+ name: "mocha",
242
+ detect: ".mocharc.json",
243
+ cmd: "mocha",
244
+ priority: 7
245
+ }
246
+ ]
247
+ },
248
+ lint: {
249
+ detectFiles: [
250
+ "biome.json",
251
+ "biome.jsonc",
252
+ ".eslintrc.js",
253
+ ".eslintrc.json"
254
+ ],
255
+ linters: [
256
+ {
257
+ name: "biome",
258
+ detect: "biome.json",
259
+ cmd: "biome check --write .",
260
+ priority: 10
261
+ },
262
+ {
263
+ name: "eslint",
264
+ detect: ".eslintrc.js",
265
+ cmd: "eslint --fix .",
266
+ priority: 9
267
+ }
268
+ ]
269
+ },
270
+ audit: {
271
+ detectFiles: ["package.json"],
272
+ command: "npm audit --json",
273
+ outputFormat: "json"
274
+ },
275
+ sast: { nativeRuleSet: "javascript", semgrepSupport: "ga" },
276
+ prompts: {
277
+ coderConstraints: [
278
+ "Use strict TypeScript; no implicit any or type assertions without justification",
279
+ "Prefer async/await over raw Promises; always handle rejections",
280
+ "Use const/let, never var; prefer immutable data structures",
281
+ "Follow existing import style (ESM); no require() in .ts files",
282
+ "Add JSDoc for all exported functions and types"
283
+ ],
284
+ reviewerChecklist: [
285
+ "Verify no implicit any or unsafe type casts",
286
+ "Check async error handling \u2014 unhandled Promises are bugs",
287
+ "Confirm ESM import consistency (no mixed require/import)",
288
+ "Validate exported API surface matches declared types",
289
+ "Check for missing null/undefined guards on optional fields"
290
+ ]
291
+ }
292
+ });
293
+ LANGUAGE_REGISTRY.register({
294
+ id: "python",
295
+ displayName: "Python",
296
+ tier: 1,
297
+ extensions: [".py", ".pyw"],
298
+ treeSitter: {
299
+ grammarId: "python",
300
+ wasmFile: "tree-sitter-python.wasm",
301
+ commentNodes: ["comment"]
302
+ },
303
+ build: {
304
+ detectFiles: ["setup.py", "pyproject.toml", "setup.cfg"],
305
+ commands: [
306
+ {
307
+ name: "pip install",
308
+ cmd: "pip install -e .",
309
+ detectFile: "setup.py",
310
+ priority: 10
311
+ },
312
+ {
313
+ name: "build",
314
+ cmd: "python -m build",
315
+ detectFile: "pyproject.toml",
316
+ priority: 9
317
+ }
318
+ ]
319
+ },
320
+ test: {
321
+ detectFiles: ["pytest.ini", "pyproject.toml", "setup.cfg", "conftest.py"],
322
+ frameworks: [
323
+ { name: "pytest", detect: "pytest.ini", cmd: "pytest", priority: 10 },
324
+ {
325
+ name: "unittest",
326
+ detect: "setup.py",
327
+ cmd: "python -m unittest discover",
328
+ priority: 8
329
+ }
330
+ ]
331
+ },
332
+ lint: {
333
+ detectFiles: ["pyproject.toml", ".ruff.toml", "setup.cfg"],
334
+ linters: [
335
+ {
336
+ name: "ruff",
337
+ detect: "pyproject.toml",
338
+ cmd: "ruff check --fix .",
339
+ priority: 10
340
+ },
341
+ { name: "flake8", detect: "setup.cfg", cmd: "flake8 .", priority: 8 }
342
+ ]
343
+ },
344
+ audit: {
345
+ detectFiles: ["requirements.txt", "Pipfile.lock", "pyproject.toml"],
346
+ command: "pip-audit --format json",
347
+ outputFormat: "json"
348
+ },
349
+ sast: { nativeRuleSet: "python", semgrepSupport: "ga" },
350
+ prompts: {
351
+ coderConstraints: [
352
+ "Use type annotations on all function signatures (PEP 484)",
353
+ "Prefer f-strings over .format() or % formatting",
354
+ "Use pathlib.Path over os.path for filesystem operations",
355
+ "Never use bare except:; catch specific exception types",
356
+ "Follow PEP 8 style; max line length 120 characters"
357
+ ],
358
+ reviewerChecklist: [
359
+ "Verify type annotations are present on all public functions",
360
+ "Check for bare except clauses or overly broad exception handling",
361
+ "Confirm no mutable default arguments (def f(x=[]) anti-pattern)",
362
+ "Validate f-string usage and no format string injection risks",
363
+ "Check imports are organized (stdlib \u2192 third-party \u2192 local)"
364
+ ]
365
+ }
366
+ });
367
+ LANGUAGE_REGISTRY.register({
368
+ id: "rust",
369
+ displayName: "Rust",
370
+ tier: 1,
371
+ extensions: [".rs"],
372
+ treeSitter: {
373
+ grammarId: "rust",
374
+ wasmFile: "tree-sitter-rust.wasm",
375
+ commentNodes: ["line_comment", "block_comment"]
376
+ },
377
+ build: {
378
+ detectFiles: ["Cargo.toml"],
379
+ commands: [
380
+ {
381
+ name: "cargo build",
382
+ cmd: "cargo build",
383
+ detectFile: "Cargo.toml",
384
+ priority: 10
385
+ },
386
+ {
387
+ name: "cargo check",
388
+ cmd: "cargo check",
389
+ detectFile: "Cargo.toml",
390
+ priority: 9
391
+ }
392
+ ]
393
+ },
394
+ test: {
395
+ detectFiles: ["Cargo.toml"],
396
+ frameworks: [
397
+ {
398
+ name: "cargo",
399
+ detect: "Cargo.toml",
400
+ cmd: "cargo test",
401
+ priority: 10
402
+ }
403
+ ]
404
+ },
405
+ lint: {
406
+ detectFiles: ["Cargo.toml"],
407
+ linters: [
408
+ {
409
+ name: "clippy",
410
+ detect: "Cargo.toml",
411
+ cmd: "cargo clippy --fix --allow-dirty",
412
+ priority: 10
413
+ },
414
+ { name: "rustfmt", detect: "Cargo.toml", cmd: "cargo fmt", priority: 9 }
415
+ ]
416
+ },
417
+ audit: {
418
+ detectFiles: ["Cargo.lock"],
419
+ command: "cargo audit --json",
420
+ outputFormat: "json"
421
+ },
422
+ sast: { nativeRuleSet: "rust", semgrepSupport: "ga" },
423
+ prompts: {
424
+ coderConstraints: [
425
+ "Prefer owned types over references where ownership is clear; use lifetimes sparingly",
426
+ "Use Result<T, E> and ? operator for error propagation; never unwrap() in library code",
427
+ "Prefer iterators and combinators over explicit loops",
428
+ "Derive standard traits (Debug, Clone, PartialEq) whenever sensible",
429
+ "Avoid unsafe blocks; document any unsafe usage with SAFETY comments"
430
+ ],
431
+ reviewerChecklist: [
432
+ "Verify no unwrap() or expect() calls in library/production paths",
433
+ "Check for potential panics in indexing, arithmetic overflow, or slice operations",
434
+ "Confirm error types implement std::error::Error and are propagated correctly",
435
+ "Validate lifetime annotations are minimal and correct",
436
+ "Check unsafe blocks have SAFETY comments explaining invariants"
437
+ ]
438
+ }
439
+ });
440
+ LANGUAGE_REGISTRY.register({
441
+ id: "go",
442
+ displayName: "Go",
443
+ tier: 1,
444
+ extensions: [".go"],
445
+ treeSitter: {
446
+ grammarId: "go",
447
+ wasmFile: "tree-sitter-go.wasm",
448
+ commentNodes: ["comment"]
449
+ },
450
+ build: {
451
+ detectFiles: ["go.mod"],
452
+ commands: [
453
+ {
454
+ name: "go build",
455
+ cmd: "go build ./...",
456
+ detectFile: "go.mod",
457
+ priority: 10
458
+ },
459
+ {
460
+ name: "go vet",
461
+ cmd: "go vet ./...",
462
+ detectFile: "go.mod",
463
+ priority: 9
464
+ }
465
+ ]
466
+ },
467
+ test: {
468
+ detectFiles: ["go.mod"],
469
+ frameworks: [
470
+ { name: "go-test", detect: "go.mod", cmd: "go test ./...", priority: 10 }
471
+ ]
472
+ },
473
+ lint: {
474
+ detectFiles: ["go.mod", ".golangci.yml", ".golangci.yaml"],
475
+ linters: [
476
+ {
477
+ name: "golangci-lint",
478
+ detect: ".golangci.yml",
479
+ cmd: "golangci-lint run --fix",
480
+ priority: 10
481
+ },
482
+ { name: "gofmt", detect: "go.mod", cmd: "gofmt -w .", priority: 9 }
483
+ ]
484
+ },
485
+ audit: {
486
+ detectFiles: ["go.mod"],
487
+ command: "govulncheck -json ./...",
488
+ outputFormat: "json"
489
+ },
490
+ sast: { nativeRuleSet: "go", semgrepSupport: "ga" },
491
+ prompts: {
492
+ coderConstraints: [
493
+ "Always check and return errors; never discard error return values",
494
+ "Use idiomatic Go error wrapping with fmt.Errorf and %w verb",
495
+ "Prefer table-driven tests; use t.Run for subtests",
496
+ "Avoid global mutable state; use dependency injection via interfaces",
497
+ "Use context.Context as first parameter for all I/O-bound functions"
498
+ ],
499
+ reviewerChecklist: [
500
+ "Verify all error return values are checked (no _ = err pattern)",
501
+ "Check goroutine usage \u2014 confirm no goroutine leaks or missing WaitGroups",
502
+ "Validate context propagation through call chains",
503
+ "Confirm exported types/functions have doc comments",
504
+ "Check for data races in concurrent code (verify sync usage)"
505
+ ]
506
+ }
507
+ });
508
+ LANGUAGE_REGISTRY.register({
509
+ id: "java",
510
+ displayName: "Java",
511
+ tier: 2,
512
+ extensions: [".java"],
513
+ treeSitter: {
514
+ grammarId: "java",
515
+ wasmFile: "tree-sitter-java.wasm",
516
+ commentNodes: ["line_comment", "block_comment"]
517
+ },
518
+ build: {
519
+ detectFiles: ["pom.xml", "build.gradle", "build.gradle.kts"],
520
+ commands: [
521
+ {
522
+ name: "maven",
523
+ cmd: "mvn compile -q",
524
+ detectFile: "pom.xml",
525
+ priority: 10
526
+ },
527
+ {
528
+ name: "gradle",
529
+ cmd: "gradle build -q",
530
+ detectFile: "build.gradle",
531
+ priority: 9
532
+ },
533
+ {
534
+ name: "gradle-kts",
535
+ cmd: "gradle build -q",
536
+ detectFile: "build.gradle.kts",
537
+ priority: 8
538
+ }
539
+ ]
540
+ },
541
+ test: {
542
+ detectFiles: ["pom.xml", "build.gradle", "build.gradle.kts"],
543
+ frameworks: [
544
+ {
545
+ name: "maven",
546
+ detect: "pom.xml",
547
+ cmd: "mvn test -q",
548
+ priority: 10
549
+ },
550
+ {
551
+ name: "gradle",
552
+ detect: "build.gradle",
553
+ cmd: "gradle test -q",
554
+ priority: 9
555
+ }
556
+ ]
557
+ },
558
+ lint: {
559
+ detectFiles: ["checkstyle.xml", "pom.xml"],
560
+ linters: [
561
+ {
562
+ name: "checkstyle",
563
+ detect: "checkstyle.xml",
564
+ cmd: "mvn checkstyle:check",
565
+ priority: 10
566
+ }
567
+ ]
568
+ },
569
+ audit: {
570
+ detectFiles: ["pom.xml", "build.gradle"],
571
+ command: null,
572
+ outputFormat: "json"
573
+ },
574
+ sast: { nativeRuleSet: "java", semgrepSupport: "ga" },
575
+ prompts: {
576
+ coderConstraints: [
577
+ "Use checked exceptions for recoverable conditions; unchecked for programming errors",
578
+ "Prefer composition over inheritance; favor interfaces over abstract classes",
579
+ "Use Optional<T> for nullable return values rather than returning null",
580
+ "Follow Java naming conventions: PascalCase classes, camelCase methods/fields",
581
+ "Close resources in try-with-resources blocks; never rely on finalizers"
582
+ ],
583
+ reviewerChecklist: [
584
+ "Check for unclosed resources \u2014 verify try-with-resources or explicit close()",
585
+ "Verify null checks are present for all external/injected dependencies",
586
+ "Confirm thread safety for shared mutable state (synchronized, volatile, atomic)",
587
+ "Validate exception handling \u2014 no swallowed exceptions or empty catch blocks",
588
+ "Check for proper equals()/hashCode() overrides on value objects"
589
+ ]
590
+ }
591
+ });
592
+ LANGUAGE_REGISTRY.register({
593
+ id: "kotlin",
594
+ displayName: "Kotlin",
595
+ tier: 2,
596
+ extensions: [".kt", ".kts"],
597
+ treeSitter: {
598
+ grammarId: "kotlin",
599
+ wasmFile: "tree-sitter-kotlin.wasm",
600
+ commentNodes: ["line_comment", "multiline_comment"]
601
+ },
602
+ build: {
603
+ detectFiles: ["build.gradle.kts", "build.gradle", "pom.xml"],
604
+ commands: [
605
+ {
606
+ name: "gradle-kts",
607
+ cmd: "gradle build -q",
608
+ detectFile: "build.gradle.kts",
609
+ priority: 10
610
+ },
611
+ {
612
+ name: "gradle",
613
+ cmd: "gradle build -q",
614
+ detectFile: "build.gradle",
615
+ priority: 9
616
+ },
617
+ {
618
+ name: "maven",
619
+ cmd: "mvn compile -q",
620
+ detectFile: "pom.xml",
621
+ priority: 8
622
+ }
623
+ ]
624
+ },
625
+ test: {
626
+ detectFiles: ["build.gradle.kts", "build.gradle"],
627
+ frameworks: [
628
+ {
629
+ name: "gradle",
630
+ detect: "build.gradle.kts",
631
+ cmd: "gradle test -q",
632
+ priority: 10
633
+ },
634
+ {
635
+ name: "gradle",
636
+ detect: "build.gradle",
637
+ cmd: "gradle test -q",
638
+ priority: 9
639
+ }
640
+ ]
641
+ },
642
+ lint: {
643
+ detectFiles: ["build.gradle.kts", "build.gradle"],
644
+ linters: [
645
+ {
646
+ name: "ktlint",
647
+ detect: "build.gradle.kts",
648
+ cmd: "ktlint --format",
649
+ priority: 10
650
+ }
651
+ ]
652
+ },
653
+ audit: {
654
+ detectFiles: ["build.gradle.kts", "build.gradle"],
655
+ command: null,
656
+ outputFormat: "json"
657
+ },
658
+ sast: { nativeRuleSet: null, semgrepSupport: "beta" },
659
+ prompts: {
660
+ coderConstraints: [
661
+ "Prefer val over var; use data classes for value objects",
662
+ "Use Kotlin coroutines for async operations; avoid blocking calls on Dispatchers.Main",
663
+ "Leverage extension functions instead of utility classes",
664
+ "Use sealed classes for exhaustive when expressions",
665
+ "Avoid platform types; always declare explicit nullability in API surfaces"
666
+ ],
667
+ reviewerChecklist: [
668
+ "Verify no non-null assertions (!!) in production code without justification",
669
+ "Check coroutine scope lifecycle \u2014 confirm scopes are cancelled when no longer needed",
670
+ "Validate when expressions are exhaustive (sealed class coverage)",
671
+ "Confirm data class equals/hashCode/copy semantics are appropriate",
672
+ "Check for blocking I/O on the wrong coroutine dispatcher"
673
+ ]
674
+ }
675
+ });
676
+ LANGUAGE_REGISTRY.register({
677
+ id: "csharp",
678
+ displayName: "C# / .NET",
679
+ tier: 2,
680
+ extensions: [".cs", ".csx"],
681
+ treeSitter: {
682
+ grammarId: "csharp",
683
+ wasmFile: "tree-sitter-c-sharp.wasm",
684
+ commentNodes: ["comment"]
685
+ },
686
+ build: {
687
+ detectFiles: ["*.csproj", "*.sln", "Directory.Build.props"],
688
+ commands: [
689
+ {
690
+ name: "dotnet build",
691
+ cmd: "dotnet build -v quiet",
692
+ detectFile: "*.csproj",
693
+ priority: 10
694
+ },
695
+ {
696
+ name: "dotnet build sln",
697
+ cmd: "dotnet build -v quiet",
698
+ detectFile: "*.sln",
699
+ priority: 9
700
+ }
701
+ ]
702
+ },
703
+ test: {
704
+ detectFiles: ["*.csproj", "*.sln"],
705
+ frameworks: [
706
+ {
707
+ name: "dotnet-test",
708
+ detect: "*.csproj",
709
+ cmd: "dotnet test",
710
+ priority: 10
711
+ }
712
+ ]
713
+ },
714
+ lint: {
715
+ detectFiles: ["*.csproj", ".editorconfig"],
716
+ linters: [
717
+ {
718
+ name: "dotnet-format",
719
+ detect: "*.csproj",
720
+ cmd: "dotnet format",
721
+ priority: 10
722
+ }
723
+ ]
724
+ },
725
+ audit: {
726
+ detectFiles: ["*.csproj", "packages.lock.json"],
727
+ command: "dotnet list package --vulnerable --include-transitive",
728
+ outputFormat: "text"
729
+ },
730
+ sast: { nativeRuleSet: "csharp", semgrepSupport: "ga" },
731
+ prompts: {
732
+ coderConstraints: [
733
+ "Use async/await throughout; avoid .Result or .Wait() which can deadlock",
734
+ "Prefer records for immutable value types; use struct only for small value types",
735
+ "Use nullable reference types (NRT) \u2014 enable <Nullable>enable</Nullable>",
736
+ "Dispose IDisposable resources with using statements or declarations",
737
+ "Follow C# naming conventions: PascalCase for public members, _camelCase for private fields"
738
+ ],
739
+ reviewerChecklist: [
740
+ "Verify no .Result or .Wait() calls that could cause deadlocks",
741
+ "Check all IDisposable are properly disposed (using / IAsyncDisposable)",
742
+ "Confirm nullable reference type annotations are present and correct",
743
+ "Validate LINQ queries do not cause N+1 query patterns in EF contexts",
744
+ "Check for missing ConfigureAwait(false) in library code"
745
+ ]
746
+ }
747
+ });
748
+ LANGUAGE_REGISTRY.register({
749
+ id: "cpp",
750
+ displayName: "C / C++",
751
+ tier: 2,
752
+ extensions: [".c", ".h", ".cpp", ".hpp", ".cc", ".cxx"],
753
+ treeSitter: {
754
+ grammarId: "cpp",
755
+ wasmFile: "tree-sitter-cpp.wasm",
756
+ commentNodes: ["comment"]
757
+ },
758
+ build: {
759
+ detectFiles: ["CMakeLists.txt", "Makefile", "meson.build"],
760
+ commands: [
761
+ {
762
+ name: "cmake",
763
+ cmd: "cmake --build build",
764
+ detectFile: "CMakeLists.txt",
765
+ priority: 10
766
+ },
767
+ { name: "make", cmd: "make", detectFile: "Makefile", priority: 9 },
768
+ {
769
+ name: "meson",
770
+ cmd: "meson compile -C builddir",
771
+ detectFile: "meson.build",
772
+ priority: 8
773
+ }
774
+ ]
775
+ },
776
+ test: {
777
+ detectFiles: ["CMakeLists.txt"],
778
+ frameworks: [
779
+ {
780
+ name: "ctest",
781
+ detect: "CMakeLists.txt",
782
+ cmd: "ctest --test-dir build",
783
+ priority: 10
784
+ }
785
+ ]
786
+ },
787
+ lint: {
788
+ detectFiles: [".clang-tidy", "CMakeLists.txt"],
789
+ linters: [
790
+ {
791
+ name: "cppcheck",
792
+ detect: "CMakeLists.txt",
793
+ cmd: "cppcheck --error-exitcode=1 .",
794
+ priority: 10
795
+ },
796
+ {
797
+ name: "clang-tidy",
798
+ detect: ".clang-tidy",
799
+ cmd: "clang-tidy -p build",
800
+ priority: 9
801
+ }
802
+ ]
803
+ },
804
+ audit: {
805
+ detectFiles: ["CMakeLists.txt", "vcpkg.json", "conanfile.txt"],
806
+ command: null,
807
+ outputFormat: "json"
808
+ },
809
+ sast: { nativeRuleSet: "cpp", semgrepSupport: "experimental" },
810
+ prompts: {
811
+ coderConstraints: [
812
+ "Prefer RAII and smart pointers (unique_ptr, shared_ptr) over raw pointers",
813
+ "Use const-correctness throughout; mark all non-mutating methods const",
814
+ "Avoid undefined behaviour: no out-of-bounds access, no use-after-free",
815
+ "Initialize all variables; prefer in-class initializers for member variables",
816
+ "Use std::array or std::vector instead of C-style arrays"
817
+ ],
818
+ reviewerChecklist: [
819
+ "Verify no raw owning pointers; confirm smart pointer ownership semantics",
820
+ "Check for memory leaks \u2014 every new has a corresponding delete or smart pointer",
821
+ "Validate bounds checking on all array/buffer accesses",
822
+ "Confirm no undefined behavior patterns (signed overflow, strict aliasing violations)",
823
+ "Check thread safety for shared data (mutex, atomic, lock-free patterns)"
824
+ ]
825
+ }
826
+ });
827
+ LANGUAGE_REGISTRY.register({
828
+ id: "swift",
829
+ displayName: "Swift",
830
+ tier: 2,
831
+ extensions: [".swift"],
832
+ treeSitter: {
833
+ grammarId: "swift",
834
+ wasmFile: "tree-sitter-swift.wasm",
835
+ commentNodes: ["comment", "multiline_comment"]
836
+ },
837
+ build: {
838
+ detectFiles: ["Package.swift", "*.xcodeproj", "*.xcworkspace"],
839
+ commands: [
840
+ {
841
+ name: "swift build",
842
+ cmd: "swift build",
843
+ detectFile: "Package.swift",
844
+ priority: 10
845
+ },
846
+ {
847
+ name: "xcodebuild",
848
+ cmd: "xcodebuild build -quiet",
849
+ detectFile: "*.xcodeproj",
850
+ priority: 9
851
+ }
852
+ ]
853
+ },
854
+ test: {
855
+ detectFiles: ["Package.swift", "*.xcodeproj"],
856
+ frameworks: [
857
+ {
858
+ name: "swift-test",
859
+ detect: "Package.swift",
860
+ cmd: "swift test",
861
+ priority: 10
862
+ },
863
+ {
864
+ name: "xcodebuild-test",
865
+ detect: "*.xcodeproj",
866
+ cmd: "xcodebuild test -quiet",
867
+ priority: 9
868
+ }
869
+ ]
870
+ },
871
+ lint: {
872
+ detectFiles: [".swiftlint.yml", "Package.swift"],
873
+ linters: [
874
+ {
875
+ name: "swiftlint",
876
+ detect: ".swiftlint.yml",
877
+ cmd: "swiftlint --fix",
878
+ priority: 10
879
+ }
880
+ ]
881
+ },
882
+ audit: {
883
+ detectFiles: ["Package.resolved", "Package.swift"],
884
+ command: null,
885
+ outputFormat: "json"
886
+ },
887
+ sast: { nativeRuleSet: null, semgrepSupport: "experimental" },
888
+ prompts: {
889
+ coderConstraints: [
890
+ "Prefer value types (structs, enums) over classes; use classes only for reference semantics",
891
+ "Use Swift concurrency (async/await, actors) over GCD for new code",
892
+ "Avoid force unwrap (!) and force cast (as!); use guard let or if let",
893
+ "Use Swift Package Manager for dependencies where possible",
894
+ "Mark types and methods as final when subclassing is not intended"
895
+ ],
896
+ reviewerChecklist: [
897
+ "Verify no force unwraps (!) or force casts (as!) in production code",
898
+ "Check for retain cycles in closures \u2014 confirm [weak self] where needed",
899
+ "Validate Swift concurrency usage \u2014 no data races across actor boundaries",
900
+ "Confirm proper error handling with do-try-catch or Result<T,E>",
901
+ "Check value vs reference type choice is appropriate for the use case"
902
+ ]
903
+ }
904
+ });
905
+ LANGUAGE_REGISTRY.register({
906
+ id: "dart",
907
+ displayName: "Dart / Flutter",
908
+ tier: 3,
909
+ extensions: [".dart"],
910
+ treeSitter: {
911
+ grammarId: "dart",
912
+ wasmFile: "tree-sitter-dart.wasm",
913
+ commentNodes: ["comment", "documentation_comment"]
914
+ },
915
+ build: {
916
+ detectFiles: ["pubspec.yaml"],
917
+ commands: [
918
+ {
919
+ name: "flutter build",
920
+ cmd: "flutter build apk",
921
+ detectFile: "pubspec.yaml",
922
+ priority: 10
923
+ },
924
+ {
925
+ name: "dart compile",
926
+ cmd: "dart compile exe .",
927
+ detectFile: "pubspec.yaml",
928
+ priority: 9
929
+ }
930
+ ]
931
+ },
932
+ test: {
933
+ detectFiles: ["pubspec.yaml"],
934
+ frameworks: [
935
+ {
936
+ name: "flutter test",
937
+ detect: "pubspec.yaml",
938
+ cmd: "flutter test",
939
+ priority: 10
940
+ },
941
+ {
942
+ name: "dart test",
943
+ detect: "pubspec.yaml",
944
+ cmd: "dart test",
945
+ priority: 9
946
+ }
947
+ ]
948
+ },
949
+ lint: {
950
+ detectFiles: ["analysis_options.yaml", "pubspec.yaml"],
951
+ linters: [
952
+ {
953
+ name: "dart analyze",
954
+ detect: "pubspec.yaml",
955
+ cmd: "dart analyze",
956
+ priority: 10
957
+ }
958
+ ]
959
+ },
960
+ audit: {
961
+ detectFiles: ["pubspec.yaml", "pubspec.lock"],
962
+ command: "dart pub outdated --json",
963
+ outputFormat: "json"
964
+ },
965
+ sast: { nativeRuleSet: null, semgrepSupport: "none" },
966
+ prompts: {
967
+ coderConstraints: [
968
+ "Use null safety features \u2014 prefer late, required, and ? annotations over dynamic",
969
+ "Follow Dart effective style: lowerCamelCase for variables, UpperCamelCase for types",
970
+ "Prefer const constructors where possible for performance",
971
+ "Use async/await over raw Future callbacks",
972
+ "Separate business logic from UI widgets; use BLoC or Provider patterns"
973
+ ],
974
+ reviewerChecklist: [
975
+ "Verify null safety annotations are correct (no unnecessary ?)",
976
+ "Check that const constructors are used where the widget tree is static",
977
+ "Confirm async operations handle error states (catchError or try-catch)",
978
+ "Validate no direct UI state mutation outside setState or stream",
979
+ "Check for platform-specific code isolation (dart:io vs dart:html)"
980
+ ]
981
+ }
982
+ });
983
+ LANGUAGE_REGISTRY.register({
984
+ id: "ruby",
985
+ displayName: "Ruby",
986
+ tier: 3,
987
+ extensions: [".rb", ".rake", ".gemspec"],
988
+ treeSitter: {
989
+ grammarId: "ruby",
990
+ wasmFile: "tree-sitter-ruby.wasm",
991
+ commentNodes: ["comment"]
992
+ },
993
+ build: {
994
+ detectFiles: ["Gemfile", "Rakefile"],
995
+ commands: [
996
+ {
997
+ name: "bundle install",
998
+ cmd: "bundle install",
999
+ detectFile: "Gemfile",
1000
+ priority: 10
1001
+ },
1002
+ { name: "rake", cmd: "rake", detectFile: "Rakefile", priority: 9 }
1003
+ ]
1004
+ },
1005
+ test: {
1006
+ detectFiles: ["Gemfile", ".rspec", "spec/spec_helper.rb"],
1007
+ frameworks: [
1008
+ {
1009
+ name: "rspec",
1010
+ detect: ".rspec",
1011
+ cmd: "bundle exec rspec",
1012
+ priority: 10
1013
+ },
1014
+ {
1015
+ name: "minitest",
1016
+ detect: "Rakefile",
1017
+ cmd: "bundle exec rake test",
1018
+ priority: 9
1019
+ }
1020
+ ]
1021
+ },
1022
+ lint: {
1023
+ detectFiles: [".rubocop.yml", "Gemfile"],
1024
+ linters: [
1025
+ {
1026
+ name: "rubocop",
1027
+ detect: ".rubocop.yml",
1028
+ cmd: "rubocop --autocorrect",
1029
+ priority: 10
1030
+ }
1031
+ ]
1032
+ },
1033
+ audit: {
1034
+ detectFiles: ["Gemfile.lock"],
1035
+ command: "bundle-audit check --format json",
1036
+ outputFormat: "json"
1037
+ },
1038
+ sast: { nativeRuleSet: null, semgrepSupport: "experimental" },
1039
+ prompts: {
1040
+ coderConstraints: [
1041
+ "Follow Ruby community style (Rubocop defaults); 120-char line limit",
1042
+ "Prefer symbols over strings for hash keys",
1043
+ "Use frozen_string_literal: true magic comment in all files",
1044
+ "Avoid monkey-patching core classes; prefer refinements",
1045
+ "Use keyword arguments for methods with more than 2 parameters"
1046
+ ],
1047
+ reviewerChecklist: [
1048
+ "Verify frozen_string_literal comment is present in new files",
1049
+ "Check for N+1 query patterns in ActiveRecord code",
1050
+ "Validate no eval or send with user-controlled input (code injection risk)",
1051
+ "Confirm exception handling is specific \u2014 no bare rescue",
1052
+ "Check for missing validations on ActiveRecord models"
1053
+ ]
1054
+ }
1055
+ });
1056
+ LANGUAGE_REGISTRY.register({
1057
+ id: "php",
1058
+ displayName: "PHP",
1059
+ tier: 3,
1060
+ extensions: [".php", ".phtml", ".blade.php"],
1061
+ treeSitter: {
1062
+ grammarId: "php",
1063
+ wasmFile: "tree-sitter-php.wasm",
1064
+ commentNodes: ["comment"]
1065
+ },
1066
+ build: {
1067
+ detectFiles: ["composer.json"],
1068
+ commands: [
1069
+ {
1070
+ name: "Composer Install",
1071
+ cmd: "composer install --no-interaction --prefer-dist",
1072
+ detectFile: "composer.json",
1073
+ priority: 10
1074
+ }
1075
+ ]
1076
+ },
1077
+ test: {
1078
+ detectFiles: ["Pest.php", "phpunit.xml", "phpunit.xml.dist"],
1079
+ frameworks: [
1080
+ {
1081
+ name: "Pest",
1082
+ detect: "Pest.php",
1083
+ cmd: "vendor/bin/pest",
1084
+ priority: 10
1085
+ },
1086
+ {
1087
+ name: "PHPUnit",
1088
+ detect: "phpunit.xml",
1089
+ cmd: "vendor/bin/phpunit",
1090
+ priority: 8
1091
+ },
1092
+ {
1093
+ name: "PHPUnit",
1094
+ detect: "phpunit.xml.dist",
1095
+ cmd: "vendor/bin/phpunit",
1096
+ priority: 7
1097
+ }
1098
+ ]
1099
+ },
1100
+ lint: {
1101
+ detectFiles: [
1102
+ "phpstan.neon",
1103
+ "phpstan.neon.dist",
1104
+ "pint.json",
1105
+ ".php-cs-fixer.php",
1106
+ "phpcs.xml"
1107
+ ],
1108
+ linters: [
1109
+ {
1110
+ name: "PHPStan",
1111
+ detect: "phpstan.neon",
1112
+ cmd: "vendor/bin/phpstan analyse",
1113
+ priority: 10
1114
+ },
1115
+ {
1116
+ name: "PHPStan",
1117
+ detect: "phpstan.neon.dist",
1118
+ cmd: "vendor/bin/phpstan analyse",
1119
+ priority: 9
1120
+ },
1121
+ {
1122
+ name: "Pint",
1123
+ detect: "pint.json",
1124
+ cmd: "vendor/bin/pint --test",
1125
+ priority: 8
1126
+ },
1127
+ {
1128
+ name: "PHP-CS-Fixer",
1129
+ detect: ".php-cs-fixer.php",
1130
+ cmd: "vendor/bin/php-cs-fixer fix --dry-run --diff",
1131
+ priority: 7
1132
+ }
1133
+ ]
1134
+ },
1135
+ audit: {
1136
+ detectFiles: ["composer.lock"],
1137
+ command: "composer audit --locked --format=json",
1138
+ outputFormat: "json"
1139
+ },
1140
+ sast: { nativeRuleSet: "php", semgrepSupport: "ga" },
1141
+ prompts: {
1142
+ coderConstraints: [
1143
+ "Follow PSR-12 coding standards",
1144
+ "Use strict types declaration: declare(strict_types=1)",
1145
+ "Prefer type hints and return type declarations on all functions",
1146
+ "Use dependency injection over static methods and singletons",
1147
+ "Prefer named constructors and value objects over primitive obsession"
1148
+ ],
1149
+ reviewerChecklist: [
1150
+ "Verify no user input reaches SQL queries without parameterised binding",
1151
+ "Check for XSS \u2014 all output must be escaped with htmlspecialchars()",
1152
+ "Confirm no eval(), exec(), or shell_exec() with user-controlled input",
1153
+ "Validate proper error handling \u2014 no bare catch blocks that swallow errors",
1154
+ "Challenge any PHP/Laravel documentation or README claim that exceeds what is implemented and CI-verified in v6.49.0 (composer build, PHPUnit/Pest/artisan test, Pint/PHP-CS-Fixer lint, PHPStan static analysis, composer audit, Laravel detection, Blade scanning, 3 Laravel SAST rules). If a PR adds docs claiming broader support, verify it is backed by tests."
1155
+ ],
1156
+ testConstraints: [
1157
+ "Prefer feature tests for HTTP, middleware, and authentication flows \u2014 use Laravel RefreshDatabase or DatabaseTransactions traits",
1158
+ "Use unit tests for isolated business logic classes that do not require the full Laravel application container",
1159
+ "Pest and PHPUnit coexist in many Laravel repos \u2014 php artisan test runs both; do not assume PHPUnit-only",
1160
+ "Use .env.testing for test environment configuration; run php artisan config:clear when environment changes affect tests",
1161
+ "For database tests, prefer RefreshDatabase over manual setUp/tearDown to avoid state leakage between tests",
1162
+ "Run php artisan config:clear and php artisan cache:clear before running tests if environment variables changed",
1163
+ "Use separate .env.testing file for test-specific configuration; never rely on .env for CI test runs",
1164
+ "For parallel testing with php artisan test --parallel, ensure database connections use separate databases per worker (APP_ENV=testing + test database suffix pattern)"
1165
+ ]
1166
+ }
1167
+ });
1168
+
1169
+ // src/lang/detector.ts
1170
+ function getProfileForFile(filePath) {
1171
+ const ext = extname(filePath);
1172
+ if (!ext)
1173
+ return;
1174
+ return LANGUAGE_REGISTRY.getByExtension(ext);
1175
+ }
1176
+ async function detectProjectLanguages(projectDir) {
1177
+ const detected = new Set;
1178
+ async function scanDir(dir) {
1179
+ let dirEntries;
1180
+ let entries;
1181
+ try {
1182
+ dirEntries = await readdir(dir, { withFileTypes: true });
1183
+ entries = dirEntries.map((e) => e.name);
1184
+ } catch {
1185
+ return;
1186
+ }
1187
+ for (const profile of LANGUAGE_REGISTRY.getAll()) {
1188
+ for (const detectFile of profile.build.detectFiles) {
1189
+ if (detectFile.includes("*") || detectFile.includes("?")) {
1190
+ const regex = new RegExp(`^${detectFile.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
1191
+ if (dirEntries.some((e) => !e.isDirectory() && regex.test(e.name))) {
1192
+ detected.add(profile.id);
1193
+ break;
1194
+ }
1195
+ continue;
1196
+ }
1197
+ try {
1198
+ await access(join(dir, detectFile));
1199
+ detected.add(profile.id);
1200
+ break;
1201
+ } catch {}
1202
+ }
1203
+ }
1204
+ for (const entry of entries) {
1205
+ const ext = extname(entry);
1206
+ if (!ext)
1207
+ continue;
1208
+ const profile = LANGUAGE_REGISTRY.getByExtension(ext);
1209
+ if (profile) {
1210
+ detected.add(profile.id);
1211
+ }
1212
+ }
1213
+ }
1214
+ await scanDir(projectDir);
1215
+ try {
1216
+ const topEntries = await readdir(projectDir, { withFileTypes: true });
1217
+ for (const entry of topEntries) {
1218
+ if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
1219
+ await scanDir(join(projectDir, entry.name));
1220
+ }
1221
+ }
1222
+ } catch {}
1223
+ const result = [];
1224
+ for (const id of detected) {
1225
+ const profile = LANGUAGE_REGISTRY.getById(id);
1226
+ if (profile)
1227
+ result.push(profile);
1228
+ }
1229
+ result.sort((a, b) => a.tier - b.tier);
1230
+ return result;
1231
+ }
1232
+
1233
+ // src/lang/default-backend.ts
1234
+ import * as fs3 from "fs";
1235
+ import * as path3 from "path";
1236
+
1237
+ // src/build/command-resolution.ts
1238
+ import * as fs from "fs";
1239
+ import * as path from "path";
1240
+ var WINDOWS_LOCAL_SHIM_CANDIDATES = [
1241
+ { suffix: ".cmd", requiresNode: true },
1242
+ { suffix: ".exe", requiresNode: false },
1243
+ { suffix: ".bat", requiresNode: true },
1244
+ { suffix: ".ps1", requiresNode: true },
1245
+ { suffix: "", requiresNode: true }
1246
+ ];
1247
+ function tokenizeCommand(command) {
1248
+ const out = [];
1249
+ let buf = "";
1250
+ let quote = null;
1251
+ for (const ch of command.trim()) {
1252
+ if (quote) {
1253
+ if (ch === quote) {
1254
+ quote = null;
1255
+ } else {
1256
+ buf += ch;
1257
+ }
1258
+ continue;
1259
+ }
1260
+ if (ch === '"' || ch === "'") {
1261
+ quote = ch;
1262
+ continue;
1263
+ }
1264
+ if (ch === " " || ch === "\t") {
1265
+ if (buf.length > 0) {
1266
+ out.push(buf);
1267
+ buf = "";
1268
+ }
1269
+ continue;
1270
+ }
1271
+ buf += ch;
1272
+ }
1273
+ if (buf.length > 0)
1274
+ out.push(buf);
1275
+ return out;
1276
+ }
1277
+ function findCommandSuffixStart(command) {
1278
+ let quote = null;
1279
+ let sawTokenChar = false;
1280
+ for (let i = 0;i < command.length; i++) {
1281
+ const ch = command[i];
1282
+ if (quote) {
1283
+ if (ch === quote) {
1284
+ quote = null;
1285
+ } else {
1286
+ sawTokenChar = true;
1287
+ }
1288
+ continue;
1289
+ }
1290
+ if (ch === '"' || ch === "'") {
1291
+ quote = ch;
1292
+ sawTokenChar = true;
1293
+ continue;
1294
+ }
1295
+ if ((ch === " " || ch === "\t") && sawTokenChar) {
1296
+ return i;
1297
+ }
1298
+ if (ch !== " " && ch !== "\t") {
1299
+ sawTokenChar = true;
1300
+ }
1301
+ }
1302
+ return command.length;
1303
+ }
1304
+ function resolveLocalCandidate(binary, workingDir, platform = process.platform, isAvailable) {
1305
+ const candidates = platform === "win32" ? WINDOWS_LOCAL_SHIM_CANDIDATES : [{ suffix: "", requiresNode: true }];
1306
+ for (const candidate of candidates) {
1307
+ const executable = `${binary}${candidate.suffix}`;
1308
+ const absolute = path.join(workingDir, "node_modules", ".bin", executable);
1309
+ if (!fs.existsSync(absolute))
1310
+ continue;
1311
+ if (candidate.requiresNode && isAvailable && !isAvailable("node"))
1312
+ continue;
1313
+ const relative = platform === "win32" ? path.win32.join("node_modules", ".bin", executable) : path.posix.join("node_modules", ".bin", executable);
1314
+ return {
1315
+ absolute,
1316
+ relative,
1317
+ requiresNode: candidate.requiresNode
1318
+ };
1319
+ }
1320
+ return null;
1321
+ }
1322
+ function hasAnyLocalCandidate(binary, workingDir, platform = process.platform) {
1323
+ const candidates = platform === "win32" ? WINDOWS_LOCAL_SHIM_CANDIDATES : [{ suffix: "", requiresNode: true }];
1324
+ for (const candidate of candidates) {
1325
+ const executable = `${binary}${candidate.suffix}`;
1326
+ if (fs.existsSync(path.join(workingDir, "node_modules", ".bin", executable))) {
1327
+ return true;
1328
+ }
1329
+ }
1330
+ return false;
1331
+ }
1332
+ function resolveLocalCommand(command, workingDir, isAvailable, platform = process.platform) {
1333
+ const tokens = tokenizeCommand(command);
1334
+ if (tokens.length === 0)
1335
+ return null;
1336
+ const [binary, ...args] = tokens;
1337
+ if (["npx", "bunx", "pnpx"].includes(binary))
1338
+ return null;
1339
+ if (binary.includes("/") || binary.includes("\\"))
1340
+ return null;
1341
+ const trimmed = command.trim();
1342
+ const local = resolveLocalCandidate(binary, workingDir, platform, isAvailable);
1343
+ if (local) {
1344
+ const shellPath = platform === "win32" ? local.relative : `./${local.relative}`;
1345
+ return {
1346
+ argv: [local.absolute, ...args],
1347
+ shellCommand: `${shellPath}${trimmed.slice(findCommandSuffixStart(trimmed))}`
1348
+ };
1349
+ }
1350
+ if (hasAnyLocalCandidate(binary, workingDir, platform))
1351
+ return null;
1352
+ return isAvailable(binary) ? { argv: tokens, shellCommand: command } : null;
1353
+ }
1354
+ function resolveLocalNodeTool(tool2, args, workingDir, platform = process.platform, isAvailable) {
1355
+ const local = resolveLocalCandidate(tool2, workingDir, platform, platform === "win32" ? isAvailable : undefined);
1356
+ if (local)
1357
+ return [local.absolute, ...args];
1358
+ if (hasAnyLocalCandidate(tool2, workingDir, platform))
1359
+ return null;
1360
+ return isAvailable?.(tool2) ? [tool2, ...args] : null;
1361
+ }
1362
+
1363
+ // src/build/discovery.ts
1364
+ import * as fs2 from "fs";
1365
+ import * as path2 from "path";
1366
+ var SUPPORTED_NODE_PACKAGE_MANAGERS = ["bun", "npm", "pnpm", "yarn"];
1367
+ var MAX_PACKAGE_MANAGER_EVIDENCE_CHARS = 120;
1368
+ var ECOSYSTEMS = [
1369
+ {
1370
+ ecosystem: "node",
1371
+ buildFiles: ["package.json"],
1372
+ toolchainCommands: [...SUPPORTED_NODE_PACKAGE_MANAGERS],
1373
+ commands: [
1374
+ { command: "npm run build", priority: 30 },
1375
+ { command: "npm run typecheck", priority: 20 },
1376
+ { command: "npm run check", priority: 10 }
1377
+ ],
1378
+ repoDefinedScripts: ["build", "typecheck", "check", "compile"]
1379
+ },
1380
+ {
1381
+ ecosystem: "rust",
1382
+ buildFiles: ["Cargo.toml"],
1383
+ toolchainCommands: ["cargo"],
1384
+ commands: [
1385
+ { command: "cargo build", priority: 20 },
1386
+ { command: "cargo check", priority: 10 }
1387
+ ]
1388
+ },
1389
+ {
1390
+ ecosystem: "go",
1391
+ buildFiles: ["go.mod"],
1392
+ toolchainCommands: ["go"],
1393
+ commands: [{ command: "go build ./...", priority: 10 }]
1394
+ },
1395
+ {
1396
+ ecosystem: "python",
1397
+ buildFiles: ["pyproject.toml", "setup.py", "setup.cfg"],
1398
+ toolchainCommands: ["python", "python3", "py"],
1399
+ commands: [
1400
+ { command: "python -m build", priority: 20 },
1401
+ { command: "python -m py_compile", priority: 10 }
1402
+ ]
1403
+ },
1404
+ {
1405
+ ecosystem: "java-maven",
1406
+ buildFiles: ["pom.xml"],
1407
+ toolchainCommands: ["mvn"],
1408
+ commands: [{ command: "mvn compile", priority: 10 }]
1409
+ },
1410
+ {
1411
+ ecosystem: "java-gradle",
1412
+ buildFiles: ["build.gradle", "build.gradle.kts", "gradle.properties"],
1413
+ toolchainCommands: ["gradle", "gradlew"],
1414
+ commands: [
1415
+ {
1416
+ command: process.platform === "win32" ? "gradlew.bat build" : "./gradlew build",
1417
+ priority: 20
1418
+ },
1419
+ { command: "gradle build", priority: 10 }
1420
+ ]
1421
+ },
1422
+ {
1423
+ ecosystem: "dotnet",
1424
+ buildFiles: ["*.csproj", "*.fsproj", "*.vbproj"],
1425
+ toolchainCommands: ["dotnet"],
1426
+ commands: [{ command: "dotnet build", priority: 10 }]
1427
+ },
1428
+ {
1429
+ ecosystem: "swift",
1430
+ buildFiles: ["Package.swift"],
1431
+ toolchainCommands: ["swift"],
1432
+ commands: [{ command: "swift build", priority: 10 }]
1433
+ },
1434
+ {
1435
+ ecosystem: "dart",
1436
+ buildFiles: ["pubspec.yaml", "pubspec.lock"],
1437
+ toolchainCommands: ["dart", "flutter"],
1438
+ commands: [
1439
+ { command: "dart compile", priority: 20 },
1440
+ { command: "dart analyze", priority: 10 }
1441
+ ]
1442
+ },
1443
+ {
1444
+ ecosystem: "cpp",
1445
+ buildFiles: ["Makefile", "CMakeLists.txt"],
1446
+ toolchainCommands: ["make", "cmake"],
1447
+ commands: [
1448
+ { command: "make", priority: 20 },
1449
+ { command: "cmake -B build && cmake --build build", priority: 10 }
1450
+ ]
1451
+ },
1452
+ {
1453
+ ecosystem: "php-composer",
1454
+ buildFiles: ["composer.json"],
1455
+ toolchainCommands: ["composer"],
1456
+ commands: [
1457
+ {
1458
+ command: "composer install --no-interaction --prefer-dist",
1459
+ priority: 10
1460
+ }
1461
+ ]
1462
+ }
1463
+ ];
1464
+ var PROFILE_TO_ECOSYSTEM_NAMES = {
1465
+ typescript: ["node"],
1466
+ javascript: ["node"],
1467
+ python: ["python"],
1468
+ rust: ["rust"],
1469
+ go: ["go"],
1470
+ java: ["java-maven", "java-gradle"],
1471
+ kotlin: ["java-gradle"],
1472
+ csharp: ["dotnet"],
1473
+ cpp: ["cpp"],
1474
+ swift: ["swift"],
1475
+ dart: ["dart"],
1476
+ ruby: [],
1477
+ php: ["php-composer"]
1478
+ };
1479
+ var toolchainCache = new Map;
1480
+ var IS_COMMAND_AVAILABLE_TIMEOUT_MS = 3000;
1481
+ function isCommandAvailable(command) {
1482
+ if (toolchainCache.has(command)) {
1483
+ return toolchainCache.get(command);
1484
+ }
1485
+ const isWindows = process.platform === "win32";
1486
+ try {
1487
+ const searchName = isWindows ? command : command;
1488
+ const result = _internals.spawnSyncImpl(isWindows ? ["where", searchName] : ["which", searchName], {
1489
+ cwd: process.cwd(),
1490
+ stdin: "ignore",
1491
+ stdout: "ignore",
1492
+ stderr: "ignore",
1493
+ timeout: IS_COMMAND_AVAILABLE_TIMEOUT_MS
1494
+ });
1495
+ const available = result.success;
1496
+ toolchainCache.set(command, available);
1497
+ return available;
1498
+ } catch {
1499
+ toolchainCache.set(command, false);
1500
+ return false;
1501
+ }
1502
+ }
1503
+ function checkToolchain(commands) {
1504
+ return commands.some((cmd) => _internals.isCommandAvailable(cmd));
1505
+ }
1506
+ function findBuildFiles(workingDir, patterns) {
1507
+ for (const pattern of patterns) {
1508
+ if (pattern.includes("*")) {
1509
+ const dir = workingDir;
1510
+ try {
1511
+ const files = fs2.readdirSync(dir);
1512
+ const regex = simpleGlobToRegex(pattern);
1513
+ const matches = files.filter((f) => regex.test(f));
1514
+ if (matches.length > 0) {
1515
+ return path2.join(dir, matches[0]);
1516
+ }
1517
+ } catch {}
1518
+ } else {
1519
+ const filePath = path2.join(workingDir, pattern);
1520
+ if (fs2.existsSync(filePath)) {
1521
+ return filePath;
1522
+ }
1523
+ }
1524
+ }
1525
+ return null;
1526
+ }
1527
+ function sanitizeEvidenceText(raw, maxChars) {
1528
+ let withoutControls = "";
1529
+ for (const char of raw) {
1530
+ const code = char.charCodeAt(0);
1531
+ withoutControls += code < 32 || code === 127 ? " " : char;
1532
+ }
1533
+ const normalized = withoutControls.replace(/\s+/g, " ").trim();
1534
+ if (!normalized)
1535
+ return "(empty)";
1536
+ if (normalized.length <= maxChars)
1537
+ return normalized;
1538
+ if (maxChars <= 3)
1539
+ return normalized.slice(0, maxChars);
1540
+ return `${normalized.slice(0, maxChars - 3)}...`;
1541
+ }
1542
+ function sanitizeRequiredCommand(command) {
1543
+ const sanitized = sanitizeEvidenceText(command, 40);
1544
+ return sanitized === "(empty)" ? null : sanitized;
1545
+ }
1546
+ function getBinaryFromCommand(command) {
1547
+ return command.trim().split(/\s+/)[0] ?? "";
1548
+ }
1549
+ function getProfileRequiredCommands(commands) {
1550
+ const uniqueCommands = new Set;
1551
+ for (const command of commands) {
1552
+ const binary = sanitizeRequiredCommand(getBinaryFromCommand(command.cmd));
1553
+ if (binary)
1554
+ uniqueCommands.add(binary);
1555
+ }
1556
+ return Array.from(uniqueCommands);
1557
+ }
1558
+ function getNoMatchingBuildFilesReason(profileId, commands) {
1559
+ const detectFiles = Array.from(new Set(commands.map((command) => command.detectFile?.trim()).filter((detectFile) => Boolean(detectFile))));
1560
+ if (detectFiles.length === 0) {
1561
+ return `No matching build files for profile ${profileId}`;
1562
+ }
1563
+ return `No matching build files for profile ${profileId}: expected ${detectFiles.join(" or ")}`;
1564
+ }
1565
+ function getRepoDefinedScripts(workingDir, scripts) {
1566
+ const packageJsonPath = path2.join(workingDir, "package.json");
1567
+ if (!fs2.existsSync(packageJsonPath)) {
1568
+ return { commands: [], hasScripts: false };
1569
+ }
1570
+ try {
1571
+ const content = fs2.readFileSync(packageJsonPath, "utf-8");
1572
+ const pkg = JSON.parse(content);
1573
+ if (!pkg.scripts || typeof pkg.scripts !== "object") {
1574
+ return { commands: [], hasScripts: false };
1575
+ }
1576
+ const scriptNames = scripts.filter((name) => typeof pkg.scripts[name] === "string" && pkg.scripts[name].trim());
1577
+ if (scriptNames.length === 0)
1578
+ return { commands: [], hasScripts: false };
1579
+ const rawPackageManager = typeof pkg.packageManager === "string" ? pkg.packageManager.trim() : undefined;
1580
+ const declared = rawPackageManager?.split("@")[0]?.trim();
1581
+ const supported = [...SUPPORTED_NODE_PACKAGE_MANAGERS];
1582
+ const sanitizedDeclared = declared ? sanitizeRequiredCommand(declared) : null;
1583
+ const candidates = declared ? [declared] : supported;
1584
+ if (rawPackageManager !== undefined && (!declared || !supported.includes(declared))) {
1585
+ return {
1586
+ commands: [],
1587
+ hasScripts: true,
1588
+ skip: {
1589
+ ecosystem: "node",
1590
+ code: "environment_unavailable",
1591
+ required_commands: sanitizedDeclared ? [sanitizedDeclared] : [],
1592
+ reason: `Unsupported packageManager: ${sanitizeEvidenceText(rawPackageManager, MAX_PACKAGE_MANAGER_EVIDENCE_CHARS)}`
1593
+ }
1594
+ };
1595
+ }
1596
+ const manager = candidates.find((candidate) => _internals.isCommandAvailable(candidate));
1597
+ if (!manager) {
1598
+ return {
1599
+ commands: [],
1600
+ hasScripts: true,
1601
+ skip: {
1602
+ ecosystem: "node",
1603
+ code: "environment_unavailable",
1604
+ required_commands: candidates,
1605
+ reason: `Package manager not available: ${candidates.join(" or ")}`
1606
+ }
1607
+ };
1608
+ }
1609
+ return {
1610
+ hasScripts: true,
1611
+ commands: scriptNames.map((scriptName) => ({
1612
+ command: `${manager} run ${scriptName}`,
1613
+ priority: 100
1614
+ }))
1615
+ };
1616
+ } catch {
1617
+ return { commands: [], hasScripts: false };
1618
+ }
1619
+ }
1620
+ function filterByScope(workingDir, scope, changedFiles) {
1621
+ if (scope === "all") {
1622
+ return findAllBuildFiles(workingDir);
1623
+ }
1624
+ if (!changedFiles || changedFiles.length === 0) {
1625
+ return [];
1626
+ }
1627
+ return changedFiles;
1628
+ }
1629
+ function findAllBuildFiles(workingDir) {
1630
+ const allBuildFiles = new Set;
1631
+ for (const ecosystem of ECOSYSTEMS) {
1632
+ for (const pattern of ecosystem.buildFiles) {
1633
+ if (pattern.includes("*")) {
1634
+ const regex = simpleGlobToRegex(pattern);
1635
+ findFilesRecursive(workingDir, regex, allBuildFiles);
1636
+ } else {
1637
+ const filePath = path2.join(workingDir, pattern);
1638
+ if (fs2.existsSync(filePath)) {
1639
+ allBuildFiles.add(filePath);
1640
+ }
1641
+ }
1642
+ }
1643
+ }
1644
+ return Array.from(allBuildFiles);
1645
+ }
1646
+ function findFilesRecursive(dir, regex, results) {
1647
+ try {
1648
+ const entries = fs2.readdirSync(dir, { withFileTypes: true });
1649
+ for (const entry of entries) {
1650
+ const fullPath = path2.join(dir, entry.name);
1651
+ if (entry.isDirectory() && !["node_modules", ".git", "dist", "build", "target"].includes(entry.name)) {
1652
+ findFilesRecursive(fullPath, regex, results);
1653
+ } else if (entry.isFile() && regex.test(entry.name)) {
1654
+ results.add(fullPath);
1655
+ }
1656
+ }
1657
+ } catch {}
1658
+ }
1659
+ var _internals = {
1660
+ isCommandAvailable,
1661
+ discoverBuildCommandsFromProfiles,
1662
+ discoverBuildCommands,
1663
+ clearToolchainCache,
1664
+ getEcosystems,
1665
+ detectProjectLanguagesImpl: detectProjectLanguages,
1666
+ getLanguageProfileImpl: LANGUAGE_REGISTRY.get.bind(LANGUAGE_REGISTRY),
1667
+ spawnSyncImpl: bunSpawnSync
1668
+ };
1669
+ async function discoverBuildCommandsFromProfiles(workingDir) {
1670
+ const commands = [];
1671
+ const skipped = [];
1672
+ const detectedProfiles = await _internals.detectProjectLanguagesImpl(workingDir);
1673
+ for (const profile of detectedProfiles) {
1674
+ const fullProfile = _internals.getLanguageProfileImpl(profile.id);
1675
+ if (!fullProfile) {
1676
+ warn(`[build-discovery] profile ${profile.id} not found in registry, skipping`);
1677
+ continue;
1678
+ }
1679
+ const sortedCommands = [...fullProfile.build.commands].sort((a, b) => b.priority - a.priority);
1680
+ const applicableCommands = sortedCommands.filter((cmd) => {
1681
+ if (!cmd.detectFile)
1682
+ return true;
1683
+ const detectFilePath = path2.join(workingDir, cmd.detectFile);
1684
+ return fs2.existsSync(detectFilePath);
1685
+ });
1686
+ if (applicableCommands.length === 0) {
1687
+ skipped.push({
1688
+ ecosystem: fullProfile.id,
1689
+ reason: getNoMatchingBuildFilesReason(fullProfile.id, sortedCommands)
1690
+ });
1691
+ continue;
1692
+ }
1693
+ let foundCommand = false;
1694
+ for (const cmd of applicableCommands) {
1695
+ const resolved = resolveLocalCommand(cmd.cmd, workingDir, _internals.isCommandAvailable);
1696
+ if (resolved) {
1697
+ commands.push({
1698
+ ecosystem: fullProfile.id,
1699
+ command: resolved.shellCommand,
1700
+ cwd: workingDir,
1701
+ priority: cmd.priority
1702
+ });
1703
+ foundCommand = true;
1704
+ break;
1705
+ }
1706
+ }
1707
+ if (!foundCommand) {
1708
+ const requiredCommands = getProfileRequiredCommands(applicableCommands);
1709
+ const triedBinaries = requiredCommands.join(", ");
1710
+ const reason = `No binary available for profile ${fullProfile.id}: tried ${triedBinaries}`;
1711
+ skipped.push({
1712
+ ecosystem: fullProfile.id,
1713
+ code: "environment_unavailable",
1714
+ required_commands: requiredCommands,
1715
+ reason
1716
+ });
1717
+ warn(`[build-discovery] profile ${fullProfile.id}: no binary available, skipping`);
1718
+ }
1719
+ }
1720
+ return { commands, skipped };
1721
+ }
1722
+ async function discoverBuildCommands(workingDir, options) {
1723
+ const scope = options?.scope ?? "all";
1724
+ const changedFiles = options?.changedFiles ?? [];
1725
+ const _filesToCheck = filterByScope(workingDir, scope, changedFiles);
1726
+ const profileResult = await _internals.discoverBuildCommandsFromProfiles(workingDir);
1727
+ const profileCommands = profileResult.commands;
1728
+ const profileSkipped = profileResult.skipped;
1729
+ const repoDefinedScriptsByEcosystem = new Map;
1730
+ const repoScriptSkips = [];
1731
+ const repoDeclaredEcosystems = new Set;
1732
+ for (const ecosystem of ECOSYSTEMS) {
1733
+ if (!ecosystem.repoDefinedScripts)
1734
+ continue;
1735
+ const repoResult = getRepoDefinedScripts(workingDir, ecosystem.repoDefinedScripts);
1736
+ if (repoResult.commands.length > 0) {
1737
+ repoDefinedScriptsByEcosystem.set(ecosystem.ecosystem, repoResult.commands);
1738
+ }
1739
+ if (repoResult.hasScripts)
1740
+ repoDeclaredEcosystems.add(ecosystem.ecosystem);
1741
+ if (repoResult.skip)
1742
+ repoScriptSkips.push(repoResult.skip);
1743
+ }
1744
+ const filteredProfileCommands = profileCommands.filter((cmd) => {
1745
+ const ecosystemNames = PROFILE_TO_ECOSYSTEM_NAMES[cmd.ecosystem] ?? [];
1746
+ return ecosystemNames.every((name) => !repoDeclaredEcosystems.has(name));
1747
+ });
1748
+ const coveredEcosystems = new Set;
1749
+ for (const cmd of filteredProfileCommands) {
1750
+ const ecosystemNames = PROFILE_TO_ECOSYSTEM_NAMES[cmd.ecosystem] ?? [];
1751
+ for (const name of ecosystemNames) {
1752
+ coveredEcosystems.add(name);
1753
+ }
1754
+ }
1755
+ const commands = [...filteredProfileCommands];
1756
+ const skipped = [...profileSkipped, ...repoScriptSkips];
1757
+ for (const ecosystem of ECOSYSTEMS) {
1758
+ const buildFile = findBuildFiles(workingDir, ecosystem.buildFiles);
1759
+ if (!buildFile)
1760
+ continue;
1761
+ const repoScripts = repoDefinedScriptsByEcosystem.get(ecosystem.ecosystem);
1762
+ if (repoScripts && repoScripts.length > 0) {
1763
+ for (const script of repoScripts) {
1764
+ commands.push({
1765
+ ecosystem: ecosystem.ecosystem,
1766
+ command: script.command,
1767
+ cwd: workingDir,
1768
+ priority: script.priority
1769
+ });
1770
+ }
1771
+ continue;
1772
+ }
1773
+ if (repoDeclaredEcosystems.has(ecosystem.ecosystem)) {
1774
+ continue;
1775
+ }
1776
+ if (coveredEcosystems.has(ecosystem.ecosystem)) {
1777
+ skipped.push({
1778
+ ecosystem: ecosystem.ecosystem,
1779
+ reason: `Covered by profile detection`
1780
+ });
1781
+ continue;
1782
+ }
1783
+ if (!checkToolchain(ecosystem.toolchainCommands)) {
1784
+ skipped.push({
1785
+ ecosystem: ecosystem.ecosystem,
1786
+ code: "environment_unavailable",
1787
+ required_commands: ecosystem.toolchainCommands,
1788
+ reason: `Toolchain not found: ${ecosystem.toolchainCommands.join(" or ")} not on PATH`
1789
+ });
1790
+ continue;
1791
+ }
1792
+ const availableCommands = ecosystem.commands;
1793
+ for (const cmd of availableCommands) {
1794
+ commands.push({
1795
+ ecosystem: ecosystem.ecosystem,
1796
+ command: cmd.command,
1797
+ cwd: workingDir,
1798
+ priority: cmd.priority
1799
+ });
1800
+ }
1801
+ }
1802
+ commands.sort((a, b) => b.priority - a.priority);
1803
+ return { commands, skipped };
1804
+ }
1805
+ function clearToolchainCache() {
1806
+ toolchainCache.clear();
1807
+ }
1808
+ function getEcosystems() {
1809
+ return ECOSYSTEMS.map((e) => e.ecosystem);
1810
+ }
1811
+ var build_discovery = tool({
1812
+ description: "Discover build commands for various ecosystems in a project directory",
1813
+ args: {
1814
+ workingDir: tool.schema.string().describe("Directory to scan for build commands (defaults to current directory)"),
1815
+ scope: tool.schema.string().optional().describe('Scope of detection: "all" for all build files, "changed" for only changed files'),
1816
+ changedFiles: tool.schema.array(tool.schema.string()).optional().describe('List of changed files when scope is "changed"')
1817
+ },
1818
+ async execute(args, _context) {
1819
+ const result = await discoverBuildCommands(args.workingDir, {
1820
+ scope: args.scope ?? "all",
1821
+ changedFiles: args.changedFiles ?? []
1822
+ });
1823
+ return JSON.stringify(result, null, 2);
1824
+ }
1825
+ });
1826
+
1827
+ // src/lang/default-backend.ts
1828
+ function detectFileExists(dir, pattern) {
1829
+ if (pattern.includes("*") || pattern.includes("?")) {
1830
+ try {
1831
+ const files = fs3.readdirSync(dir);
1832
+ const regex = new RegExp(`^${pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".")}$`);
1833
+ return files.some((f) => regex.test(f));
1834
+ } catch {
1835
+ return false;
1836
+ }
1837
+ }
1838
+ try {
1839
+ fs3.accessSync(path3.join(dir, pattern));
1840
+ return true;
1841
+ } catch {
1842
+ return false;
1843
+ }
1844
+ }
1845
+ async function defaultSelectTestFramework(profile, dir) {
1846
+ const sorted = [...profile.test.frameworks].sort((a, b) => b.priority - a.priority);
1847
+ for (const fw of sorted) {
1848
+ if (!detectFileExists(dir, fw.detect))
1849
+ continue;
1850
+ const argv = tokenizeCommand(fw.cmd);
1851
+ if (argv.length === 0)
1852
+ continue;
1853
+ if (!isCommandAvailable(argv[0]))
1854
+ continue;
1855
+ return {
1856
+ name: fw.name,
1857
+ cmd: argv,
1858
+ cwd: dir,
1859
+ detectedVia: fw.detect,
1860
+ filesIgnored: false
1861
+ };
1862
+ }
1863
+ return null;
1864
+ }
1865
+ function defaultBuildTestCommand(profile, framework, files, dir = ".", opts = {}) {
1866
+ const scope = opts.scope ?? "all";
1867
+ const coverage = opts.coverage ?? false;
1868
+ const bail = opts.bail ?? false;
1869
+ const targets = opts.targets;
1870
+ const nativeTarget = opts.nativeTarget;
1871
+ if (nativeTarget) {
1872
+ if (nativeTarget.framework !== framework)
1873
+ return null;
1874
+ return buildNativeTargetCommand(nativeTarget);
1875
+ }
1876
+ switch (framework) {
1877
+ case "bun": {
1878
+ const args = ["bun", "test"];
1879
+ if (coverage)
1880
+ args.push("--coverage");
1881
+ if (bail)
1882
+ args.push("--bail");
1883
+ if (scope !== "all" && files.length > 0)
1884
+ args.push(...files);
1885
+ return args;
1886
+ }
1887
+ case "vitest": {
1888
+ const args = resolveLocalNodeTool("vitest", [
1889
+ "run",
1890
+ "--reporter=json",
1891
+ "--outputFile",
1892
+ ".swarm/cache/test-runner-vitest.json"
1893
+ ], dir, process.platform, isCommandAvailable);
1894
+ if (!args)
1895
+ return null;
1896
+ if (coverage)
1897
+ args.push("--coverage");
1898
+ if (bail)
1899
+ args.push("--bail");
1900
+ if (scope !== "all" && files.length > 0)
1901
+ args.push(...files);
1902
+ return args;
1903
+ }
1904
+ case "jest": {
1905
+ const args = resolveLocalNodeTool("jest", ["--json"], dir, process.platform, isCommandAvailable);
1906
+ if (!args)
1907
+ return null;
1908
+ if (coverage)
1909
+ args.push("--coverage");
1910
+ if (bail)
1911
+ args.push("--bail");
1912
+ if (scope !== "all" && files.length > 0)
1913
+ args.push(...files);
1914
+ return args;
1915
+ }
1916
+ case "mocha": {
1917
+ const args = resolveLocalNodeTool("mocha", [], dir, process.platform, isCommandAvailable);
1918
+ if (!args)
1919
+ return null;
1920
+ if (bail)
1921
+ args.push("--bail");
1922
+ if (scope !== "all" && files.length > 0)
1923
+ args.push(...files);
1924
+ return args;
1925
+ }
1926
+ case "pytest": {
1927
+ const isWindows = process.platform === "win32";
1928
+ const args = isWindows ? ["python", "-m", "pytest"] : ["python3", "-m", "pytest"];
1929
+ if (coverage)
1930
+ args.push("--cov=.", "--cov-report=term-missing");
1931
+ if (bail)
1932
+ args.push("-x");
1933
+ if (scope !== "all" && files.length > 0)
1934
+ args.push(...files);
1935
+ return args;
1936
+ }
1937
+ case "cargo": {
1938
+ const args = ["cargo", "test"];
1939
+ if (targets && targets.length > 0) {
1940
+ args.push("--", ...targets);
1941
+ } else if (scope !== "all" && files.length > 0) {
1942
+ args.push(...files);
1943
+ }
1944
+ return args;
1945
+ }
1946
+ case "pester": {
1947
+ if (scope !== "all" && files.length > 0) {
1948
+ const escapedFiles = files.map((f) => f.replace(/'/g, "''").replace(/`/g, "``").replace(/\$/g, "`$"));
1949
+ const psCommand = `Invoke-Pester -Path @('${escapedFiles.join("','")}')`;
1950
+ const utf16Bytes = Buffer.from(psCommand, "utf16le");
1951
+ const base64Command = utf16Bytes.toString("base64");
1952
+ return ["pwsh", "-EncodedCommand", base64Command];
1953
+ }
1954
+ return ["pwsh", "-Command", "Invoke-Pester"];
1955
+ }
1956
+ case "go-test": {
1957
+ const args = ["go", "test"];
1958
+ if (targets && targets.length > 0) {
1959
+ args.push("-run", targets.join("|"));
1960
+ }
1961
+ args.push("./...");
1962
+ return args;
1963
+ }
1964
+ case "maven": {
1965
+ const args = ["mvn", "test"];
1966
+ if (targets && targets.length > 0) {
1967
+ args.push(`-Dtest=${targets.join(",")}`);
1968
+ }
1969
+ return args;
1970
+ }
1971
+ case "gradle": {
1972
+ const isWindows = process.platform === "win32";
1973
+ const hasGradlewBat = fs3.existsSync(path3.join(dir, "gradlew.bat"));
1974
+ const hasGradlew = fs3.existsSync(path3.join(dir, "gradlew"));
1975
+ const args = [];
1976
+ if (hasGradlewBat && isWindows)
1977
+ args.push("gradlew.bat");
1978
+ else if (hasGradlew)
1979
+ args.push("./gradlew");
1980
+ else
1981
+ args.push("gradle");
1982
+ args.push("test");
1983
+ if (targets && targets.length > 0) {
1984
+ for (const target of targets) {
1985
+ args.push("--tests", target);
1986
+ }
1987
+ }
1988
+ return args;
1989
+ }
1990
+ case "dotnet-test": {
1991
+ const args = ["dotnet", "test"];
1992
+ if (targets && targets.length > 0) {
1993
+ args.push("--filter", targets.join("|"));
1994
+ }
1995
+ return args;
1996
+ }
1997
+ case "ctest": {
1998
+ const buildDirCandidates = [
1999
+ "build",
2000
+ "_build",
2001
+ "cmake-build-debug",
2002
+ "cmake-build-release",
2003
+ "out"
2004
+ ];
2005
+ const actualBuildDir = buildDirCandidates.find((d) => fs3.existsSync(path3.join(dir, d, "CMakeCache.txt"))) ?? "build";
2006
+ const args = ["ctest", "--test-dir", actualBuildDir];
2007
+ if (targets && targets.length > 0) {
2008
+ args.push("-R", targets.join("|"));
2009
+ }
2010
+ return args;
2011
+ }
2012
+ case "swift-test": {
2013
+ const args = ["swift", "test"];
2014
+ if (targets && targets.length > 0) {
2015
+ for (const target of targets) {
2016
+ args.push("--filter", target);
2017
+ }
2018
+ }
2019
+ return args;
2020
+ }
2021
+ case "dart-test":
2022
+ return isCommandAvailable("flutter") ? ["flutter", "test", ...files] : ["dart", "test", ...files];
2023
+ case "rspec": {
2024
+ const args = isCommandAvailable("bundle") ? ["bundle", "exec", "rspec"] : ["rspec"];
2025
+ if (bail)
2026
+ args.push("--fail-fast");
2027
+ if (scope !== "all" && files.length > 0)
2028
+ args.push(...files);
2029
+ return args;
2030
+ }
2031
+ case "minitest":
2032
+ if (scope !== "all" && files.length > 0) {
2033
+ const requires = files.map((f) => `require_relative '${f.replace(/\\/g, "/").replace(/'/g, "\\'")}'`).join("; ");
2034
+ return ["ruby", "-Itest", "-e", requires];
2035
+ }
2036
+ return [
2037
+ "ruby",
2038
+ "-Itest",
2039
+ "-e",
2040
+ 'Dir.glob("test/**/*_test.rb").sort.each { |f| require_relative f }'
2041
+ ];
2042
+ case "pest": {
2043
+ const args = [phpVendorBin("pest")];
2044
+ if (scope !== "all" && files.length > 0)
2045
+ args.push(...files);
2046
+ return args;
2047
+ }
2048
+ case "phpunit": {
2049
+ const args = [phpVendorBin("phpunit")];
2050
+ if (scope !== "all" && files.length > 0)
2051
+ args.push(...files);
2052
+ return args;
2053
+ }
2054
+ case "php-artisan": {
2055
+ const args = ["php", "artisan", "test"];
2056
+ if (scope !== "all" && files.length > 0)
2057
+ args.push(...files);
2058
+ return args;
2059
+ }
2060
+ default: {
2061
+ const fw = profile.test.frameworks.find((f) => f.name === framework);
2062
+ if (!fw)
2063
+ return null;
2064
+ const argv = tokenizeCommand(fw.cmd);
2065
+ if (argv.length === 0)
2066
+ return null;
2067
+ if (files.length === 0)
2068
+ return argv;
2069
+ return [...argv, ...files];
2070
+ }
2071
+ }
2072
+ }
2073
+ function escapeRegexLiteral(value) {
2074
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2075
+ }
2076
+ function buildNativeTargetCommand(target) {
2077
+ if (target.framework === "ctest") {
2078
+ return [
2079
+ "ctest",
2080
+ "--test-dir",
2081
+ target.path,
2082
+ "-R",
2083
+ `^${escapeRegexLiteral(target.name)}$`
2084
+ ];
2085
+ }
2086
+ const selector = target.name.split("/").map((part) => `^${escapeRegexLiteral(part)}$`).join("/");
2087
+ const packagePath = target.path === "." ? "." : target.path.startsWith("./") ? target.path : `./${target.path.replace(/\\/g, "/")}`;
2088
+ return ["go", "test", "-v", "-run", selector, packagePath];
2089
+ }
2090
+ function phpVendorBin(name) {
2091
+ return path3.join("vendor", "bin", process.platform === "win32" ? `${name}.bat` : name);
2092
+ }
2093
+ function defaultParseTestOutput(framework, stdout, stderr, exitCode) {
2094
+ const output = stdout && stderr ? `${stdout}
2095
+ ${stderr}` : stdout || stderr || "";
2096
+ let passed = 0;
2097
+ let failed = 0;
2098
+ let skipped = 0;
2099
+ let total;
2100
+ let coveragePercent;
2101
+ switch (framework) {
2102
+ case "vitest":
2103
+ case "jest":
2104
+ case "bun": {
2105
+ const jsonMatch = output.match(/\{[\s\S]*"testResults"[\s\S]*\}/);
2106
+ if (jsonMatch) {
2107
+ try {
2108
+ const parsed = JSON.parse(jsonMatch[0]);
2109
+ if (parsed.numTotalTests !== undefined) {
2110
+ passed = parsed.numPassedTests || 0;
2111
+ failed = parsed.numFailedTests || 0;
2112
+ skipped = parsed.numPendingTests || 0;
2113
+ total = parsed.numTotalTests || 0;
2114
+ }
2115
+ if (parsed.coverage !== undefined) {
2116
+ coveragePercent = parsed.coverage;
2117
+ }
2118
+ } catch {}
2119
+ }
2120
+ if (total === undefined || total === 0) {
2121
+ const passMatch = output.match(/(\d+)\s+pass(ing|ed)?/);
2122
+ const failMatch = output.match(/(\d+)\s+fail(ing|ed)?/);
2123
+ const skipMatch = output.match(/(\d+)\s+skip(ping|ped)?/);
2124
+ if (passMatch)
2125
+ passed = parseInt(passMatch[1], 10);
2126
+ if (failMatch)
2127
+ failed = parseInt(failMatch[1], 10);
2128
+ if (skipMatch)
2129
+ skipped = parseInt(skipMatch[1], 10);
2130
+ total = passed + failed + skipped;
2131
+ }
2132
+ const coverageMatch = output.match(/All files[^\d]*(\d+\.?\d*)\s*%/);
2133
+ if (coveragePercent === undefined && coverageMatch) {
2134
+ coveragePercent = parseFloat(coverageMatch[1]);
2135
+ }
2136
+ break;
2137
+ }
2138
+ case "mocha": {
2139
+ const passMatch = output.match(/(\d+)\s+passing/);
2140
+ const failMatch = output.match(/(\d+)\s+failing/);
2141
+ const pendingMatch = output.match(/(\d+)\s+pending/);
2142
+ if (passMatch)
2143
+ passed = parseInt(passMatch[1], 10);
2144
+ if (failMatch)
2145
+ failed = parseInt(failMatch[1], 10);
2146
+ if (pendingMatch)
2147
+ skipped = parseInt(pendingMatch[1], 10);
2148
+ total = passed + failed + skipped;
2149
+ break;
2150
+ }
2151
+ case "pytest": {
2152
+ const passMatch = output.match(/(\d+)\s+passed/);
2153
+ const failMatch = output.match(/(\d+)\s+failed/);
2154
+ const skipMatch = output.match(/(\d+)\s+skipped/);
2155
+ if (passMatch)
2156
+ passed = parseInt(passMatch[1], 10);
2157
+ if (failMatch)
2158
+ failed = parseInt(failMatch[1], 10);
2159
+ if (skipMatch)
2160
+ skipped = parseInt(skipMatch[1], 10);
2161
+ total = passed + failed + skipped;
2162
+ const coverageMatch = output.match(/TOTAL\s+(\d+\.?\d*)\s*%/);
2163
+ if (coverageMatch)
2164
+ coveragePercent = parseFloat(coverageMatch[1]);
2165
+ break;
2166
+ }
2167
+ case "cargo": {
2168
+ const passMatch = output.match(/test result: ok\. (\d+) passed/);
2169
+ const failMatch = output.match(/test result: FAILED\. (\d+) passed; (\d+) failed/);
2170
+ if (failMatch) {
2171
+ passed = parseInt(failMatch[1], 10);
2172
+ failed = parseInt(failMatch[2], 10);
2173
+ } else if (passMatch) {
2174
+ passed = parseInt(passMatch[1], 10);
2175
+ }
2176
+ total = passed + failed;
2177
+ break;
2178
+ }
2179
+ case "pester": {
2180
+ const passMatch = output.match(/Passed:\s*(\d+)/);
2181
+ const failMatch = output.match(/Failed:\s*(\d+)/);
2182
+ const skipMatch = output.match(/Skipped:\s*(\d+)/);
2183
+ if (passMatch)
2184
+ passed = parseInt(passMatch[1], 10);
2185
+ if (failMatch)
2186
+ failed = parseInt(failMatch[1], 10);
2187
+ if (skipMatch)
2188
+ skipped = parseInt(skipMatch[1], 10);
2189
+ total = passed + failed + skipped;
2190
+ break;
2191
+ }
2192
+ case "go-test": {
2193
+ const passMatches = [...output.matchAll(/--- PASS:/g)];
2194
+ const failMatches = [...output.matchAll(/--- FAIL:/g)];
2195
+ const skipMatches = [...output.matchAll(/--- SKIP:/g)];
2196
+ passed = passMatches.length;
2197
+ failed = failMatches.length;
2198
+ skipped = skipMatches.length;
2199
+ total = passed + failed + skipped;
2200
+ const covMatch = output.match(/coverage:\s*(\d+\.?\d*)\s*%/);
2201
+ if (covMatch)
2202
+ coveragePercent = parseFloat(covMatch[1]);
2203
+ break;
2204
+ }
2205
+ case "maven": {
2206
+ const mavenMatch = output.match(/Tests run:\s*(\d+),\s*Failures:\s*(\d+),\s*Errors:\s*(\d+),\s*Skipped:\s*(\d+)/);
2207
+ if (mavenMatch) {
2208
+ const tot = parseInt(mavenMatch[1], 10);
2209
+ const failures = parseInt(mavenMatch[2], 10);
2210
+ const errors = parseInt(mavenMatch[3], 10);
2211
+ const sk = parseInt(mavenMatch[4], 10);
2212
+ failed = failures + errors;
2213
+ skipped = sk;
2214
+ passed = tot - failed - sk;
2215
+ total = tot;
2216
+ }
2217
+ break;
2218
+ }
2219
+ case "gradle": {
2220
+ const gradleMatch = output.match(/(\d+) tests? completed(?:,\s*(\d+) failed)?(?:,\s*(\d+) skipped)?/);
2221
+ if (gradleMatch) {
2222
+ total = parseInt(gradleMatch[1], 10);
2223
+ failed = gradleMatch[2] ? parseInt(gradleMatch[2], 10) : 0;
2224
+ skipped = gradleMatch[3] ? parseInt(gradleMatch[3], 10) : 0;
2225
+ passed = total - failed - skipped;
2226
+ }
2227
+ break;
2228
+ }
2229
+ case "dotnet-test": {
2230
+ const passMatch = output.match(/Passed[!:]?\s*(\d+)/i);
2231
+ const failMatch = output.match(/Failed[!:]?\s*(\d+)/i);
2232
+ const skipMatch = output.match(/Skipped[!:]?\s*(\d+)/i);
2233
+ if (passMatch)
2234
+ passed = parseInt(passMatch[1], 10);
2235
+ if (failMatch)
2236
+ failed = parseInt(failMatch[1], 10);
2237
+ if (skipMatch)
2238
+ skipped = parseInt(skipMatch[1], 10);
2239
+ total = passed + failed + skipped;
2240
+ break;
2241
+ }
2242
+ case "ctest": {
2243
+ const disabledCount = [
2244
+ ...output.matchAll(/\*{3}Not Run\s+\(Disabled\)/gi)
2245
+ ].length;
2246
+ skipped = disabledCount;
2247
+ const ctestMatch = output.match(/(\d+) tests? failed out of (\d+)/);
2248
+ if (ctestMatch) {
2249
+ failed = parseInt(ctestMatch[1], 10);
2250
+ const executedTotal = parseInt(ctestMatch[2], 10);
2251
+ total = executedTotal + disabledCount;
2252
+ passed = executedTotal - failed;
2253
+ } else {
2254
+ const allPassMatch = output.match(/100% tests passed.*?(\d+) tests?/);
2255
+ if (allPassMatch) {
2256
+ const executedTotal = parseInt(allPassMatch[1], 10);
2257
+ total = executedTotal + disabledCount;
2258
+ passed = executedTotal;
2259
+ } else if (disabledCount > 0) {
2260
+ total = disabledCount;
2261
+ }
2262
+ }
2263
+ break;
2264
+ }
2265
+ case "swift-test": {
2266
+ const swiftMatch = output.match(/Executed (\d+) tests?,\s*with (\d+) failures?/);
2267
+ if (swiftMatch) {
2268
+ total = parseInt(swiftMatch[1], 10);
2269
+ failed = parseInt(swiftMatch[2], 10);
2270
+ passed = total - failed;
2271
+ }
2272
+ break;
2273
+ }
2274
+ case "dart-test": {
2275
+ const dartPassMatch = output.match(/\+(\d+):\s*All tests passed/);
2276
+ const dartMixMatch = output.match(/\+(\d+)\s+-(\d+):/);
2277
+ if (dartPassMatch) {
2278
+ passed = parseInt(dartPassMatch[1], 10);
2279
+ total = passed;
2280
+ } else if (dartMixMatch) {
2281
+ passed = parseInt(dartMixMatch[1], 10);
2282
+ failed = parseInt(dartMixMatch[2], 10);
2283
+ total = passed + failed;
2284
+ }
2285
+ break;
2286
+ }
2287
+ case "rspec": {
2288
+ const rspecMatch = output.match(/(\d+) examples?,\s*(\d+) failures?(?:,\s*(\d+) pending)?/);
2289
+ if (rspecMatch) {
2290
+ total = parseInt(rspecMatch[1], 10);
2291
+ failed = parseInt(rspecMatch[2], 10);
2292
+ skipped = rspecMatch[3] ? parseInt(rspecMatch[3], 10) : 0;
2293
+ passed = total - failed - skipped;
2294
+ }
2295
+ break;
2296
+ }
2297
+ case "minitest": {
2298
+ const minitestMatch = output.match(/(\d+) runs?,\s*\d+ assertions?,\s*(\d+) failures?,\s*(\d+) errors?,\s*(\d+) skips?/);
2299
+ if (minitestMatch) {
2300
+ total = parseInt(minitestMatch[1], 10);
2301
+ const failures = parseInt(minitestMatch[2], 10);
2302
+ const errors = parseInt(minitestMatch[3], 10);
2303
+ skipped = parseInt(minitestMatch[4], 10);
2304
+ failed = failures + errors;
2305
+ passed = total - failed - skipped;
2306
+ }
2307
+ break;
2308
+ }
2309
+ case "pest":
2310
+ case "phpunit":
2311
+ case "php-artisan": {
2312
+ const phpunitMatch = output.match(/Tests:\s*(\d+),\s*Assertions:\s*\d+(?:,\s*Failures:\s*(\d+))?(?:,\s*Errors:\s*(\d+))?(?:,\s*Skipped:\s*(\d+))?/);
2313
+ const okMatch = output.match(/OK\s*\((\d+)\s+tests?/);
2314
+ if (phpunitMatch) {
2315
+ total = parseInt(phpunitMatch[1], 10);
2316
+ const failures = phpunitMatch[2] ? parseInt(phpunitMatch[2], 10) : 0;
2317
+ const errors = phpunitMatch[3] ? parseInt(phpunitMatch[3], 10) : 0;
2318
+ skipped = phpunitMatch[4] ? parseInt(phpunitMatch[4], 10) : 0;
2319
+ failed = failures + errors;
2320
+ passed = total - failed - skipped;
2321
+ } else if (okMatch) {
2322
+ total = parseInt(okMatch[1], 10);
2323
+ passed = total;
2324
+ }
2325
+ break;
2326
+ }
2327
+ default:
2328
+ break;
2329
+ }
2330
+ if (total === undefined)
2331
+ total = passed + failed + skipped;
2332
+ return {
2333
+ ok: exitCode === 0,
2334
+ raw: { stdout, stderr, exitCode },
2335
+ passed,
2336
+ failed,
2337
+ skipped,
2338
+ total,
2339
+ ...coveragePercent !== undefined ? { coveragePercent } : {}
2340
+ };
2341
+ }
2342
+ async function defaultDetectProject(profile, dir) {
2343
+ for (const f of profile.build.detectFiles) {
2344
+ if (detectFileExists(dir, f))
2345
+ return true;
2346
+ }
2347
+ return false;
2348
+ }
2349
+ async function defaultSelectBuildCommand(profile, dir) {
2350
+ const sorted = [...profile.build.commands].sort((a, b) => b.priority - a.priority);
2351
+ for (const cmd of sorted) {
2352
+ if (cmd.detectFile && !detectFileExists(dir, cmd.detectFile))
2353
+ continue;
2354
+ const resolved = resolveLocalCommand(cmd.cmd, dir, isCommandAvailable);
2355
+ if (!resolved)
2356
+ continue;
2357
+ return {
2358
+ name: cmd.name,
2359
+ cmd: resolved.argv,
2360
+ cwd: dir,
2361
+ detectedVia: cmd.detectFile ?? `${profile.id} default`
2362
+ };
2363
+ }
2364
+ return null;
2365
+ }
2366
+ async function defaultTestFilesFor(profile, sourceFile, dir) {
2367
+ const ext = path3.extname(sourceFile);
2368
+ if (!profile.extensions.includes(ext))
2369
+ return [];
2370
+ const base = path3.basename(sourceFile, ext);
2371
+ const rel = path3.relative(dir, sourceFile);
2372
+ const relDir = path3.dirname(rel);
2373
+ const stripSrc = relDir.replace(/^src(\/|\\)/, "");
2374
+ const candidates = new Set;
2375
+ for (const tDir of ["tests", "test", "__tests__", "spec"]) {
2376
+ for (const suffix of ["", "_test", ".test", "_spec", ".spec"]) {
2377
+ candidates.add(path3.join(dir, tDir, stripSrc, `${base}${suffix}${ext}`));
2378
+ }
2379
+ }
2380
+ const existing = [];
2381
+ for (const c of candidates) {
2382
+ try {
2383
+ fs3.accessSync(c);
2384
+ existing.push(c);
2385
+ } catch {}
2386
+ }
2387
+ return existing;
2388
+ }
2389
+ function defaultExtractImports() {
2390
+ return [];
2391
+ }
2392
+ async function defaultSelectFramework() {
2393
+ return null;
2394
+ }
2395
+ async function defaultSelectEntryPoints() {
2396
+ return [];
2397
+ }
2398
+ function defaultBackendFor(profile) {
2399
+ return {
2400
+ ...profile,
2401
+ detectProject: (dir) => defaultDetectProject(profile, dir),
2402
+ selectTestFramework: (dir) => defaultSelectTestFramework(profile, dir),
2403
+ buildTestCommand: (framework, files, dir, opts) => defaultBuildTestCommand(profile, framework, files, dir, opts),
2404
+ parseTestOutput: (framework, stdout, stderr, exitCode) => defaultParseTestOutput(framework, stdout, stderr, exitCode),
2405
+ testFilesFor: (sourceFile, dir) => defaultTestFilesFor(profile, sourceFile, dir),
2406
+ extractImports: () => defaultExtractImports(),
2407
+ selectBuildCommand: (dir) => defaultSelectBuildCommand(profile, dir),
2408
+ selectFramework: () => defaultSelectFramework(),
2409
+ selectEntryPoints: () => defaultSelectEntryPoints()
2410
+ };
2411
+ }
2412
+
2413
+ // src/lang/backends/go.ts
2414
+ import * as fs4 from "fs";
2415
+ import * as path4 from "path";
2416
+ var PROFILE_ID = "go";
2417
+ var IMPORT_REGEX_SINGLE = /^\s*import\s+(?:[a-zA-Z_.][a-zA-Z0-9_]*\s+)?"([^"]+)"/gm;
2418
+ var IMPORT_REGEX_GROUP = /^\s*import\s*\(([\s\S]*?)\)/gm;
2419
+ var IMPORT_REGEX_GROUP_LINE = /(?:[a-zA-Z_.][a-zA-Z0-9_]*\s+)?"([^"]+)"/g;
2420
+ var MAX_IMPORT_SOURCE_BYTES = 1 * 1024 * 1024;
2421
+ function stripGoComments(source) {
2422
+ let state = "normal";
2423
+ let out = "";
2424
+ let escaped = false;
2425
+ for (let index = 0;index < source.length; index++) {
2426
+ const current = source[index];
2427
+ const next = source[index + 1];
2428
+ if (state === "line") {
2429
+ if (current === `
2430
+ ` || current === "\r") {
2431
+ state = "normal";
2432
+ out += current;
2433
+ } else {
2434
+ out += " ";
2435
+ }
2436
+ continue;
2437
+ }
2438
+ if (state === "block") {
2439
+ if (current === "*" && next === "/") {
2440
+ out += " ";
2441
+ index++;
2442
+ state = "normal";
2443
+ } else {
2444
+ out += current === `
2445
+ ` || current === "\r" ? current : " ";
2446
+ }
2447
+ continue;
2448
+ }
2449
+ if (state === "string") {
2450
+ out += current;
2451
+ if (escaped)
2452
+ escaped = false;
2453
+ else if (current === "\\")
2454
+ escaped = true;
2455
+ else if (current === '"')
2456
+ state = "normal";
2457
+ continue;
2458
+ }
2459
+ if (state === "raw") {
2460
+ out += current;
2461
+ if (current === "`")
2462
+ state = "normal";
2463
+ continue;
2464
+ }
2465
+ if (current === "/" && next === "/") {
2466
+ out += " ";
2467
+ index++;
2468
+ state = "line";
2469
+ } else if (current === "/" && next === "*") {
2470
+ out += " ";
2471
+ index++;
2472
+ state = "block";
2473
+ } else {
2474
+ out += current;
2475
+ if (current === '"')
2476
+ state = "string";
2477
+ else if (current === "`")
2478
+ state = "raw";
2479
+ }
2480
+ }
2481
+ return out;
2482
+ }
2483
+ function extractImports(_sourceFile, source) {
2484
+ if (Buffer.byteLength(source, "utf8") > MAX_IMPORT_SOURCE_BYTES)
2485
+ return [];
2486
+ const out = new Set;
2487
+ const uncommented = stripGoComments(source);
2488
+ IMPORT_REGEX_SINGLE.lastIndex = 0;
2489
+ let m = IMPORT_REGEX_SINGLE.exec(uncommented);
2490
+ while (m !== null) {
2491
+ out.add(m[1]);
2492
+ m = IMPORT_REGEX_SINGLE.exec(uncommented);
2493
+ }
2494
+ IMPORT_REGEX_GROUP.lastIndex = 0;
2495
+ m = IMPORT_REGEX_GROUP.exec(uncommented);
2496
+ while (m !== null) {
2497
+ const block = m[1];
2498
+ IMPORT_REGEX_GROUP_LINE.lastIndex = 0;
2499
+ let inner = IMPORT_REGEX_GROUP_LINE.exec(block);
2500
+ while (inner !== null) {
2501
+ out.add(inner[1]);
2502
+ inner = IMPORT_REGEX_GROUP_LINE.exec(block);
2503
+ }
2504
+ m = IMPORT_REGEX_GROUP.exec(uncommented);
2505
+ }
2506
+ return [...out];
2507
+ }
2508
+ async function selectFramework(dir) {
2509
+ let content;
2510
+ try {
2511
+ content = fs4.readFileSync(path4.join(dir, "go.mod"), "utf-8");
2512
+ } catch {
2513
+ return null;
2514
+ }
2515
+ const candidates = [
2516
+ ["github.com/gin-gonic/gin", "gin"],
2517
+ ["github.com/labstack/echo", "echo"],
2518
+ ["github.com/gofiber/fiber", "fiber"],
2519
+ ["github.com/gorilla/mux", "gorilla"],
2520
+ ["github.com/go-chi/chi", "chi"]
2521
+ ];
2522
+ for (const [pkg, name] of candidates) {
2523
+ if (content.includes(pkg)) {
2524
+ return { name, detectedVia: `go.mod require ${pkg}` };
2525
+ }
2526
+ }
2527
+ return null;
2528
+ }
2529
+ async function selectEntryPoints(dir) {
2530
+ const points = [];
2531
+ try {
2532
+ fs4.accessSync(path4.join(dir, "main.go"));
2533
+ points.push("main.go");
2534
+ } catch {}
2535
+ try {
2536
+ const cmdDir = path4.join(dir, "cmd");
2537
+ const subdirs = fs4.readdirSync(cmdDir, { withFileTypes: true }).filter((d) => d.isDirectory());
2538
+ for (const sub of subdirs) {
2539
+ const main = path4.join("cmd", sub.name, "main.go");
2540
+ try {
2541
+ fs4.accessSync(path4.join(dir, main));
2542
+ points.push(main);
2543
+ } catch {}
2544
+ }
2545
+ } catch {}
2546
+ return points;
2547
+ }
2548
+ function buildGoBackend() {
2549
+ const profile = LANGUAGE_REGISTRY.get(PROFILE_ID);
2550
+ if (!profile) {
2551
+ throw new Error("buildGoBackend: go profile not in LANGUAGE_REGISTRY. " + "profiles.ts must be imported before this backend.");
2552
+ }
2553
+ return {
2554
+ ...defaultBackendFor(profile),
2555
+ extractImports,
2556
+ selectFramework,
2557
+ selectEntryPoints
2558
+ };
2559
+ }
2560
+ var _internals2 = { extractImports };
2561
+
2562
+ // src/lang/backends/python.ts
2563
+ import * as fs5 from "fs";
2564
+ import * as path5 from "path";
2565
+ var PROFILE_ID2 = "python";
2566
+ var IMPORT_REGEX_FROM_WITH_TARGETS = /^\s*from\s+(\.*[\w.]*)\s+import\s+(\([^)]*\)|[^\n#]+)/gm;
2567
+ var IMPORT_REGEX_IMPORT = /^\s*import\s+([^\n#]+)/gm;
2568
+ function parseImportTargets(rawTargets) {
2569
+ const cleaned = rawTargets.replace(/[()]/g, "").split(`
2570
+ `).map((line) => line.replace(/#.*$/, "").replace(/\\\s*$/, "")).join(" ");
2571
+ const out = [];
2572
+ for (const seg of cleaned.split(",")) {
2573
+ const trimmed = seg.trim();
2574
+ if (trimmed.length === 0)
2575
+ continue;
2576
+ const name = trimmed.split(/\s+as\s+/)[0].trim();
2577
+ if (/^[\w]+$/.test(name))
2578
+ out.push(name);
2579
+ }
2580
+ return out;
2581
+ }
2582
+ function extractImports2(_sourceFile, source) {
2583
+ const out = new Set;
2584
+ source = source.replace(/\\\r?\n[ \t]*/g, " ");
2585
+ IMPORT_REGEX_FROM_WITH_TARGETS.lastIndex = 0;
2586
+ let m = IMPORT_REGEX_FROM_WITH_TARGETS.exec(source);
2587
+ while (m !== null) {
2588
+ const fromClause = m[1];
2589
+ const targets = parseImportTargets(m[2]);
2590
+ const isPurelyRelative = fromClause.length > 0 && /^\.+$/.test(fromClause);
2591
+ if (isPurelyRelative && targets.length > 0) {
2592
+ for (const t of targets)
2593
+ out.add(`${fromClause}${t}`);
2594
+ } else if (fromClause.length > 0) {
2595
+ out.add(fromClause);
2596
+ }
2597
+ m = IMPORT_REGEX_FROM_WITH_TARGETS.exec(source);
2598
+ }
2599
+ IMPORT_REGEX_IMPORT.lastIndex = 0;
2600
+ m = IMPORT_REGEX_IMPORT.exec(source);
2601
+ while (m !== null) {
2602
+ const segments = m[1].split(",");
2603
+ for (const seg of segments) {
2604
+ const trimmed = seg.trim();
2605
+ if (trimmed.length === 0)
2606
+ continue;
2607
+ const mod = trimmed.split(/\s+as\s+/)[0].trim();
2608
+ if (/^[\w.]+$/.test(mod))
2609
+ out.add(mod);
2610
+ }
2611
+ m = IMPORT_REGEX_IMPORT.exec(source);
2612
+ }
2613
+ return [...out];
2614
+ }
2615
+ async function selectFramework2(dir) {
2616
+ const candidates = [
2617
+ ["django", "django"],
2618
+ ["flask", "flask"],
2619
+ ["fastapi", "fastapi"],
2620
+ ["starlette", "starlette"],
2621
+ ["tornado", "tornado"],
2622
+ ["aiohttp", "aiohttp"],
2623
+ ["bottle", "bottle"]
2624
+ ];
2625
+ for (const candidate of ["pyproject.toml", "requirements.txt", "setup.py"]) {
2626
+ try {
2627
+ const content = fs5.readFileSync(path5.join(dir, candidate), "utf-8");
2628
+ const lower = content.toLowerCase();
2629
+ for (const [pkg, name] of candidates) {
2630
+ if (lower.includes(pkg)) {
2631
+ return { name, detectedVia: candidate };
2632
+ }
2633
+ }
2634
+ } catch {}
2635
+ }
2636
+ return null;
2637
+ }
2638
+ async function selectEntryPoints2(dir) {
2639
+ const points = new Set;
2640
+ try {
2641
+ const content = fs5.readFileSync(path5.join(dir, "pyproject.toml"), "utf-8");
2642
+ const scriptsBlock = content.match(/\[project\.scripts\][\s\S]*?(?=\n\[|$)/);
2643
+ if (scriptsBlock) {
2644
+ for (const line of scriptsBlock[0].split(`
2645
+ `)) {
2646
+ const m = line.match(/=\s*['"]([^'":]+)/);
2647
+ if (m) {
2648
+ const modPath = `${m[1].replace(/\./g, "/")}.py`;
2649
+ points.add(modPath);
2650
+ }
2651
+ }
2652
+ }
2653
+ } catch {}
2654
+ for (const name of ["manage.py", "main.py", "app.py", "__main__.py"]) {
2655
+ try {
2656
+ fs5.accessSync(path5.join(dir, name));
2657
+ points.add(name);
2658
+ } catch {}
2659
+ }
2660
+ return [...points];
2661
+ }
2662
+ function buildPythonBackend() {
2663
+ const profile = LANGUAGE_REGISTRY.get(PROFILE_ID2);
2664
+ if (!profile) {
2665
+ throw new Error("buildPythonBackend: python profile not in LANGUAGE_REGISTRY. " + "profiles.ts must be imported before this backend.");
2666
+ }
2667
+ return {
2668
+ ...defaultBackendFor(profile),
2669
+ extractImports: extractImports2,
2670
+ selectFramework: selectFramework2,
2671
+ selectEntryPoints: selectEntryPoints2
2672
+ };
2673
+ }
2674
+ var _internals3 = { extractImports: extractImports2 };
2675
+
2676
+ export { LANGUAGE_REGISTRY, getProfileForFile, detectProjectLanguages, tokenizeCommand, resolveLocalNodeTool, isCommandAvailable, defaultSelectTestFramework, defaultBuildTestCommand, buildNativeTargetCommand, defaultParseTestOutput, defaultSelectBuildCommand, defaultTestFilesFor, defaultBackendFor, buildGoBackend, _internals2 as _internals, buildPythonBackend, _internals3 as _internals1 };