@wrongstack/tools 0.313.0 → 0.313.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/audit.js +10 -4055
  2. package/dist/bash.js +9 -2308
  3. package/dist/batch-tool-use.js +2 -187
  4. package/dist/browser/index.js +25 -1339
  5. package/dist/builtin.js +69 -33949
  6. package/dist/chunk-2CTB3UK5.js +197 -0
  7. package/dist/chunk-2ME7ZWDD.js +1272 -0
  8. package/dist/chunk-2TFQDEPU.js +113 -0
  9. package/dist/chunk-2ZCJNKFE.js +315 -0
  10. package/dist/chunk-3PVYQFRQ.js +472 -0
  11. package/dist/chunk-3QMQBO5Z.js +89 -0
  12. package/dist/chunk-42AKKX7O.js +385 -0
  13. package/dist/chunk-42SKEETB.js +113 -0
  14. package/dist/chunk-4BHUSMNL.js +104 -0
  15. package/dist/chunk-4I4LWFOO.js +431 -0
  16. package/dist/chunk-553IQUMO.js +478 -0
  17. package/dist/chunk-55KF77OK.js +1944 -0
  18. package/dist/chunk-6HLZK37X.js +359 -0
  19. package/dist/chunk-6RNIFLG4.js +305 -0
  20. package/dist/chunk-A6BRWIXN.js +317 -0
  21. package/dist/chunk-AHHDWNXC.js +200 -0
  22. package/dist/chunk-B2CIOKWP.js +899 -0
  23. package/dist/chunk-BTSFTGIL.js +7 -0
  24. package/dist/chunk-D33S4RQG.js +222 -0
  25. package/dist/chunk-DIJKHSUO.js +134 -0
  26. package/dist/chunk-DPL47QGS.js +3515 -0
  27. package/dist/chunk-EMRGN4KM.js +170 -0
  28. package/dist/chunk-FGOABE4Z.js +955 -0
  29. package/dist/chunk-FW65TP4G.js +520 -0
  30. package/dist/chunk-GOIZNNOA.js +221 -0
  31. package/dist/chunk-H5MJUAA6.js +295 -0
  32. package/dist/chunk-H5OHH6F4.js +223 -0
  33. package/dist/chunk-HC5RM4WY.js +199 -0
  34. package/dist/chunk-HEQFMLZ7.js +190 -0
  35. package/dist/chunk-HFXSNKCV.js +196 -0
  36. package/dist/chunk-HXSBQ2SE.js +1723 -0
  37. package/dist/chunk-ILESNGNM.js +163 -0
  38. package/dist/chunk-INF5CGAO.js +137 -0
  39. package/dist/chunk-IWDFZ6XR.js +337 -0
  40. package/dist/chunk-IXMNTNW5.js +238 -0
  41. package/dist/chunk-J4SRJKBW.js +428 -0
  42. package/dist/chunk-J5SKD6LN.js +235 -0
  43. package/dist/chunk-JRJJX3L5.js +424 -0
  44. package/dist/chunk-KXNDHMUB.js +366 -0
  45. package/dist/chunk-LIJQMYYX.js +59 -0
  46. package/dist/chunk-MAN26PZ5.js +260 -0
  47. package/dist/chunk-MKRQMLZA.js +474 -0
  48. package/dist/chunk-NBDZMMG2.js +169 -0
  49. package/dist/chunk-NHA6BEIH.js +101 -0
  50. package/dist/chunk-OA6YJIWL.js +4412 -0
  51. package/dist/chunk-OZC255IC.js +2830 -0
  52. package/dist/chunk-PUR6WU2B.js +154 -0
  53. package/dist/chunk-Q5JPPCZC.js +12 -0
  54. package/dist/chunk-QBV725YG.js +128 -0
  55. package/dist/chunk-QHVW4AAM.js +892 -0
  56. package/dist/chunk-QNJ7AQ4B.js +1000 -0
  57. package/dist/chunk-RGISBZBD.js +231 -0
  58. package/dist/chunk-RJZQWEMS.js +84 -0
  59. package/dist/chunk-RP2ZA47V.js +15 -0
  60. package/dist/chunk-RXISBFR5.js +315 -0
  61. package/dist/chunk-S5CCLZL3.js +99 -0
  62. package/dist/chunk-TVTCSBJF.js +122 -0
  63. package/dist/chunk-U4D5S3VQ.js +21 -0
  64. package/dist/chunk-U4VAATKP.js +193 -0
  65. package/dist/chunk-UKU2ZX4C.js +203 -0
  66. package/dist/chunk-UNSKG6DC.js +159 -0
  67. package/dist/chunk-URFKEIHM.js +1091 -0
  68. package/dist/chunk-UTV7YAW7.js +178 -0
  69. package/dist/chunk-V43EQ6VR.js +68 -0
  70. package/dist/chunk-VB3MJGCV.js +610 -0
  71. package/dist/chunk-WHIIIREL.js +223 -0
  72. package/dist/chunk-XMIFYKUT.js +1371 -0
  73. package/dist/chunk-YF2RSV34.js +54 -0
  74. package/dist/chunk-Z6DAKQ3U.js +1 -0
  75. package/dist/chunk-ZIGAZFWE.js +522 -0
  76. package/dist/circuit-breaker.js +3 -191
  77. package/dist/codebase-index/index.js +84 -13688
  78. package/dist/codebase-index/parser-worker-script.js +8 -3995
  79. package/dist/codebase-index/project-server.js +60 -8548
  80. package/dist/codebase-index/worker.js +20 -8416
  81. package/dist/design.js +2 -299
  82. package/dist/diff.js +4 -256
  83. package/dist/document.js +4 -222
  84. package/dist/e2e.js +5 -476
  85. package/dist/edit.js +14 -10200
  86. package/dist/exec.js +16 -3131
  87. package/dist/fetch.js +8 -398
  88. package/dist/format.js +10 -3750
  89. package/dist/generic-parser-VUXDIAOZ.js +13 -0
  90. package/dist/git.js +4 -380
  91. package/dist/glob.js +6 -280
  92. package/dist/go-parser-6CA77WZ4.js +469 -0
  93. package/dist/grep.js +7 -604
  94. package/dist/index.js +318 -35387
  95. package/dist/install.js +10 -4091
  96. package/dist/json-parser-7YMDJSBJ.js +213 -0
  97. package/dist/json.js +4 -665
  98. package/dist/kanban.js +3 -2096
  99. package/dist/languages/index.js +23 -4664
  100. package/dist/legacy-bridge-7UKZT46G.js +18 -0
  101. package/dist/lint.js +10 -3706
  102. package/dist/logs.js +4 -287
  103. package/dist/memory.js +6 -215
  104. package/dist/mode.js +3 -99
  105. package/dist/outdated.js +4 -5170
  106. package/dist/pack.js +65 -33897
  107. package/dist/parser-dispatch-VPOFDO2U.js +14 -0
  108. package/dist/patch.js +13 -9935
  109. package/dist/plan.js +5 -3286
  110. package/dist/process-registry.js +8 -651
  111. package/dist/ps-slash.js +12 -1439
  112. package/dist/py-parser-GPM5VYRG.js +16 -0
  113. package/dist/read.js +13 -9816
  114. package/dist/replace.js +15 -10014
  115. package/dist/rs-parser-WVQ4KYCX.js +81 -0
  116. package/dist/scaffold.js +4 -253
  117. package/dist/search.js +5 -585
  118. package/dist/session-kanban.js +30 -972
  119. package/dist/skill.js +2 -215
  120. package/dist/task.js +5 -3410
  121. package/dist/test.js +10 -3773
  122. package/dist/todo.js +4 -2891
  123. package/dist/tool-help.js +3 -123
  124. package/dist/tool-search.js +3 -79
  125. package/dist/tool-tier.js +68 -33984
  126. package/dist/tool-use.js +2 -93
  127. package/dist/tree-sitter-parser-YCILHGZD.js +17 -0
  128. package/dist/tree.js +4 -288
  129. package/dist/ts-parser-YH4ZMWMH.js +232 -0
  130. package/dist/typecheck.js +10 -3753
  131. package/dist/win32.js +6 -60
  132. package/dist/write.js +14 -9882
  133. package/dist/yaml-parser-GFAOKV53.js +157 -0
  134. package/package.json +5 -5
@@ -0,0 +1,3515 @@
1
+ import {
2
+ spawnStream
3
+ } from "./chunk-MAN26PZ5.js";
4
+ import {
5
+ normalizeCommandOutput,
6
+ safeResolveReal
7
+ } from "./chunk-D33S4RQG.js";
8
+
9
+ // src/languages/detect.ts
10
+ import { createHash } from "node:crypto";
11
+ import * as fs from "node:fs/promises";
12
+ import * as path from "node:path";
13
+
14
+ // src/languages/profile-helpers.ts
15
+ var DEFAULT_TIMEOUT_MS = 12e4;
16
+ var DEFAULT_OUTPUT_LIMIT_BYTES = 2e5;
17
+ function processPlan(ctx, operation, command, args, options) {
18
+ return {
19
+ profileId: ctx.workspace.language,
20
+ workspaceId: ctx.workspace.id,
21
+ operation,
22
+ kind: "process",
23
+ command,
24
+ args: Object.freeze([...args]),
25
+ cwd: ctx.workspace.root,
26
+ env: Object.freeze({}),
27
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
28
+ outputLimitBytes: DEFAULT_OUTPUT_LIMIT_BYTES,
29
+ mutating: options.mutating ?? false,
30
+ network: options.network ?? false,
31
+ executesProjectCode: options.executesProjectCode ?? false,
32
+ reason: options.reason,
33
+ evidence: ctx.workspace.evidence,
34
+ parser: options.parser
35
+ };
36
+ }
37
+ function internalPlan(ctx, operation, parser, reason) {
38
+ return {
39
+ profileId: ctx.workspace.language,
40
+ workspaceId: ctx.workspace.id,
41
+ operation,
42
+ kind: "internal",
43
+ command: null,
44
+ args: Object.freeze([]),
45
+ cwd: ctx.workspace.root,
46
+ env: Object.freeze({}),
47
+ timeoutMs: 5e3,
48
+ outputLimitBytes: 32768,
49
+ mutating: false,
50
+ network: false,
51
+ executesProjectCode: false,
52
+ reason,
53
+ evidence: ctx.workspace.evidence,
54
+ parser
55
+ };
56
+ }
57
+ function unavailable(ctx, operation, reason) {
58
+ return {
59
+ status: "unavailable",
60
+ profileId: ctx.workspace.language,
61
+ workspaceId: ctx.workspace.id,
62
+ operation,
63
+ reason
64
+ };
65
+ }
66
+ function packageNames(ctx) {
67
+ return ctx.options.packages ?? [];
68
+ }
69
+
70
+ // src/languages/profiles/additional.ts
71
+ var IGNORES = Object.freeze([
72
+ ".git",
73
+ ".wrongstack",
74
+ "node_modules",
75
+ "vendor",
76
+ "dist",
77
+ "build",
78
+ "coverage"
79
+ ]);
80
+ function pythonProfile() {
81
+ return {
82
+ id: "python",
83
+ displayName: "Python",
84
+ extensions: Object.freeze([".py", ".pyi"]),
85
+ lspLanguageIds: Object.freeze(["python"]),
86
+ detectors: Object.freeze([
87
+ { kind: "config", filename: "pyproject.toml", weight: 90 },
88
+ { kind: "config", filename: "setup.py", weight: 70 },
89
+ { kind: "config", filename: "setup.cfg", weight: 60 },
90
+ { kind: "manifest", filename: "requirements.txt", weight: 55 },
91
+ { kind: "manifest", filename: "Pipfile", weight: 50 },
92
+ { kind: "lockfile", filename: "poetry.lock", weight: 30 },
93
+ { kind: "lockfile", filename: "Pipfile.lock", weight: 30 },
94
+ { kind: "lockfile", filename: "uv.lock", weight: 30 }
95
+ ]),
96
+ ignoredDirectories: IGNORES,
97
+ packageManagers: Object.freeze(["pip", "poetry", "pipenv", "uv"]),
98
+ executables: Object.freeze([
99
+ "python",
100
+ "python3",
101
+ "pip",
102
+ "pip3",
103
+ "poetry",
104
+ "pipenv",
105
+ "uv",
106
+ "ruff",
107
+ "black",
108
+ "pytest",
109
+ "mypy",
110
+ "pyright"
111
+ ]),
112
+ operations: Object.freeze({
113
+ syntax: async (ctx) => {
114
+ const py = ctx.options.target ? "python3" : "python3";
115
+ return ctx.options.target ? processPlan(ctx, "syntax", py, ["-m", "py_compile", ctx.options.target], {
116
+ parser: "python",
117
+ reason: "Compile the target file to check for syntax errors."
118
+ }) : unavailable(ctx, "syntax", "Python syntax check requires an explicit target file.");
119
+ },
120
+ semantic: async (ctx) => processPlan(ctx, "semantic", "mypy", [".", "--no-error-summary"], {
121
+ parser: "mypy",
122
+ reason: "Run mypy type checking on the workspace.",
123
+ executesProjectCode: true
124
+ }),
125
+ lint: async (ctx) => processPlan(ctx, "lint", "ruff", ["check", "."], {
126
+ parser: "ruff",
127
+ reason: "Run the Ruff linter on the workspace."
128
+ }),
129
+ "format-check": async (ctx) => processPlan(ctx, "format-check", "ruff", ["format", "--check", "."], {
130
+ parser: "ruff",
131
+ reason: "Check Python formatting without writing files."
132
+ }),
133
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "ruff", ["format", "."], {
134
+ parser: "ruff",
135
+ reason: "Format Python source files.",
136
+ mutating: true
137
+ }),
138
+ test: async (ctx) => processPlan(
139
+ ctx,
140
+ "test",
141
+ "pytest",
142
+ [...ctx.options.filter ? ["-k", ctx.options.filter] : [], "."],
143
+ {
144
+ parser: "pytest",
145
+ reason: ctx.options.filter ? "Run filtered Python tests." : "Run all Python tests.",
146
+ mutating: true,
147
+ executesProjectCode: true
148
+ }
149
+ ),
150
+ build: async (ctx) => unavailable(ctx, "build", "Python is interpreted; use semantic or test instead."),
151
+ "debug-compile": async (ctx) => processPlan(ctx, "debug-compile", "mypy", [".", "--no-error-summary"], {
152
+ parser: "mypy",
153
+ reason: "Collect mypy type diagnostics.",
154
+ executesProjectCode: true
155
+ }),
156
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "pip", ["install", "--no-cache-dir"], {
157
+ parser: "package-text",
158
+ reason: "Install dependencies from requirements.",
159
+ mutating: true,
160
+ network: true
161
+ }),
162
+ "package-add": async (ctx) => {
163
+ const names = packageNames(ctx);
164
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one package name is required.") : processPlan(ctx, "package-add", "pip", ["install", ...names], {
165
+ parser: "package-text",
166
+ reason: "Install specified Python packages.",
167
+ mutating: true,
168
+ network: true
169
+ });
170
+ },
171
+ "package-remove": async (ctx) => {
172
+ const names = packageNames(ctx);
173
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one package name is required.") : processPlan(ctx, "package-remove", "pip", ["uninstall", "--yes", ...names], {
174
+ parser: "package-text",
175
+ reason: "Remove validated Python packages.",
176
+ mutating: true
177
+ });
178
+ },
179
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "pip", ["audit"], {
180
+ parser: "pip-audit",
181
+ reason: "Audit Python dependencies for vulnerabilities.",
182
+ network: true
183
+ }),
184
+ run: async (ctx) => {
185
+ const entries = ["main.py", "app.py", "__main__.py", "manage.py"];
186
+ const first = (await Promise.all(
187
+ entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
188
+ )).find(Boolean);
189
+ const args = first ? [first] : [];
190
+ return processPlan(ctx, "run", "python3", args, {
191
+ parser: "command-text",
192
+ reason: first ? `Run the Python entry point ${first}.` : "Run the Python project (no common entry point detected \u2014 add the module path manually).",
193
+ executesProjectCode: true,
194
+ mutating: true
195
+ });
196
+ }
197
+ })
198
+ };
199
+ }
200
+ function hasGradleEvidence(ctx) {
201
+ return ctx.workspace.evidence.some(
202
+ (e) => e.kind === "manifest" && (e.value === "build.gradle" || e.value === "build.gradle.kts" || e.value === "settings.gradle") || e.kind === "lockfile" && e.value === "gradle.lockfile"
203
+ );
204
+ }
205
+ async function gradleRunner(ctx) {
206
+ if (await ctx.pathExists("gradlew")) return "gradlew";
207
+ return "gradle";
208
+ }
209
+ function javaProfile() {
210
+ return {
211
+ id: "java",
212
+ displayName: "Java / Kotlin",
213
+ extensions: Object.freeze([".java", ".kt", ".kts", ".scala"]),
214
+ lspLanguageIds: Object.freeze(["java", "kotlin"]),
215
+ detectors: Object.freeze([
216
+ { kind: "manifest", filename: "pom.xml", weight: 90 },
217
+ { kind: "manifest", filename: "build.gradle", weight: 85 },
218
+ { kind: "manifest", filename: "build.gradle.kts", weight: 85 },
219
+ { kind: "manifest", filename: "settings.gradle", weight: 50 },
220
+ { kind: "lockfile", filename: "gradle.lockfile", weight: 30 }
221
+ ]),
222
+ ignoredDirectories: IGNORES,
223
+ packageManagers: Object.freeze(["maven", "gradle"]),
224
+ executables: Object.freeze(["mvn", "gradle", "gradlew", "java", "kotlinc"]),
225
+ operations: Object.freeze({
226
+ semantic: async (ctx) => {
227
+ const isGradle = hasGradleEvidence(ctx);
228
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
229
+ const args = isGradle ? ["compileJava"] : ["compile", "-q"];
230
+ const reason = isGradle ? "Compile the Gradle project." : "Compile the Maven project.";
231
+ return processPlan(ctx, "semantic", runner, args, {
232
+ parser: isGradle ? "gradle" : "maven",
233
+ reason,
234
+ mutating: true,
235
+ executesProjectCode: true
236
+ });
237
+ },
238
+ lint: async (ctx) => unavailable(
239
+ ctx,
240
+ "lint",
241
+ "Java linting requires a configured checkstyle or spotbugs plugin."
242
+ ),
243
+ "format-check": async (ctx) => unavailable(
244
+ ctx,
245
+ "format-check",
246
+ "Java formatting check requires a configured spotless or google-java-format plugin."
247
+ ),
248
+ test: async (ctx) => {
249
+ const isGradle = hasGradleEvidence(ctx);
250
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
251
+ const args = isGradle ? ["test"] : ["test", "-q"];
252
+ const reason = isGradle ? "Run Gradle tests." : "Run Maven tests.";
253
+ return processPlan(ctx, "test", runner, args, {
254
+ parser: isGradle ? "gradle" : "maven",
255
+ reason,
256
+ mutating: true,
257
+ executesProjectCode: true
258
+ });
259
+ },
260
+ build: async (ctx) => {
261
+ const isGradle = hasGradleEvidence(ctx);
262
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
263
+ const args = isGradle ? ["build"] : ["package", "-q", "-DskipTests"];
264
+ const reason = isGradle ? "Build the Gradle project." : "Build the Maven project, skipping tests.";
265
+ return processPlan(ctx, "build", runner, args, {
266
+ parser: isGradle ? "gradle" : "maven",
267
+ reason,
268
+ mutating: true,
269
+ executesProjectCode: true
270
+ });
271
+ },
272
+ run: async (ctx) => {
273
+ if (!hasGradleEvidence(ctx))
274
+ return unavailable(
275
+ ctx,
276
+ "run",
277
+ "Maven run requires exec-maven-plugin configuration. Use `mvn exec:java -q` manually."
278
+ );
279
+ const runner = await gradleRunner(ctx);
280
+ return processPlan(ctx, "run", runner, ["run"], {
281
+ parser: "command-text",
282
+ reason: `Run the Gradle project entry point via ${runner}.`,
283
+ executesProjectCode: true,
284
+ mutating: true
285
+ });
286
+ },
287
+ "package-install": async (ctx) => unavailable(
288
+ ctx,
289
+ "package-install",
290
+ "JVM dependency installation happens through build or dependency:get."
291
+ ),
292
+ "package-add": async (ctx) => {
293
+ const names = packageNames(ctx);
294
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one dependency coordinate is required.") : processPlan(ctx, "package-add", "mvn", ["dependency:get", `-Dartifact=${names[0]}`], {
295
+ parser: "maven",
296
+ reason: "Resolve and fetch a Maven dependency.",
297
+ mutating: true,
298
+ network: true
299
+ });
300
+ }
301
+ })
302
+ };
303
+ }
304
+ function rubyProfile() {
305
+ return {
306
+ id: "ruby",
307
+ displayName: "Ruby",
308
+ extensions: Object.freeze([".rb"]),
309
+ lspLanguageIds: Object.freeze(["ruby"]),
310
+ detectors: Object.freeze([
311
+ { kind: "manifest", filename: "Gemfile", weight: 90 },
312
+ { kind: "config", filename: ".ruby-version", weight: 40 },
313
+ { kind: "lockfile", filename: "Gemfile.lock", weight: 30 }
314
+ ]),
315
+ ignoredDirectories: IGNORES,
316
+ packageManagers: Object.freeze(["gem", "bundler"]),
317
+ executables: Object.freeze(["ruby", "gem", "bundle", "rubocop", "rspec"]),
318
+ operations: Object.freeze({
319
+ syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "ruby", ["-c", ctx.options.target], {
320
+ parser: "ruby",
321
+ reason: "Check the target Ruby file for syntax errors."
322
+ }) : unavailable(ctx, "syntax", "Ruby syntax check requires an explicit target file."),
323
+ lint: async (ctx) => processPlan(ctx, "lint", "rubocop", ["--format=json"], {
324
+ parser: "rubocop",
325
+ reason: "Run RuboCop linter.",
326
+ executesProjectCode: true
327
+ }),
328
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "rubocop", ["--auto-correct"], {
329
+ parser: "rubocop",
330
+ reason: "Auto-correct RuboCop violations.",
331
+ mutating: true,
332
+ executesProjectCode: true
333
+ }),
334
+ test: async (ctx) => processPlan(ctx, "test", "rspec", [], {
335
+ parser: "rspec",
336
+ reason: "Run the RSpec test suite.",
337
+ mutating: true,
338
+ executesProjectCode: true
339
+ }),
340
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "bundle", ["install"], {
341
+ parser: "package-text",
342
+ reason: "Install Ruby gem dependencies.",
343
+ mutating: true,
344
+ network: true,
345
+ executesProjectCode: true
346
+ }),
347
+ "package-add": async (ctx) => {
348
+ const names = packageNames(ctx);
349
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one gem name is required.") : processPlan(ctx, "package-add", "gem", ["install", ...names], {
350
+ parser: "package-text",
351
+ reason: "Install specified Ruby gems.",
352
+ mutating: true,
353
+ network: true
354
+ });
355
+ },
356
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "bundle", ["audit", "--format=json"], {
357
+ parser: "bundler-audit",
358
+ reason: "Audit Ruby gems for vulnerabilities.",
359
+ network: true
360
+ }),
361
+ run: async (ctx) => {
362
+ const entries = ["main.rb", "app.rb", "server.rb", "config.ru"];
363
+ const first = (await Promise.all(
364
+ entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
365
+ )).find(Boolean);
366
+ const hasGemfile = await ctx.pathExists("Gemfile");
367
+ const cmd = hasGemfile ? "bundle" : "ruby";
368
+ const args = hasGemfile ? ["exec", "ruby", first ?? ""] : [first ?? ""];
369
+ return processPlan(ctx, "run", cmd, args, {
370
+ parser: "command-text",
371
+ reason: first ? `Run the Ruby entry point ${first}.` : "Run the Ruby project (no common entry point detected \u2014 add the file path manually).",
372
+ executesProjectCode: true,
373
+ mutating: true
374
+ });
375
+ }
376
+ })
377
+ };
378
+ }
379
+ function cProfile() {
380
+ return {
381
+ id: "c",
382
+ displayName: "C / C++",
383
+ extensions: Object.freeze([".c", ".h"]),
384
+ lspLanguageIds: Object.freeze(["c"]),
385
+ detectors: Object.freeze([
386
+ { kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
387
+ { kind: "manifest", filename: "Makefile", weight: 60 },
388
+ { kind: "config", suffix: ".cmake", weight: 50 }
389
+ ]),
390
+ ignoredDirectories: IGNORES,
391
+ packageManagers: Object.freeze([]),
392
+ executables: Object.freeze(["cc", "gcc", "clang", "cmake", "make"]),
393
+ operations: Object.freeze({
394
+ semantic: async (ctx) => processPlan(ctx, "semantic", "cmake", ["--build", ".", "--target", "all"], {
395
+ parser: "cmake",
396
+ reason: "Build C project to collect compiler diagnostics.",
397
+ mutating: true,
398
+ executesProjectCode: true
399
+ }),
400
+ test: async (ctx) => unavailable(
401
+ ctx,
402
+ "test",
403
+ "C test execution requires a configured test runner (ctest, etc.)."
404
+ ),
405
+ build: async (ctx) => processPlan(ctx, "build", "cmake", ["--build", "."], {
406
+ parser: "cmake",
407
+ reason: "Build the C project.",
408
+ mutating: true,
409
+ executesProjectCode: true
410
+ })
411
+ })
412
+ };
413
+ }
414
+ function cppProfile() {
415
+ return {
416
+ ...cProfile(),
417
+ id: "cpp",
418
+ displayName: "C++",
419
+ extensions: Object.freeze([".cpp", ".cc", ".cxx", ".hpp", ".hxx"]),
420
+ lspLanguageIds: Object.freeze(["cpp"]),
421
+ detectors: Object.freeze([
422
+ { kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
423
+ { kind: "manifest", filename: "Makefile", weight: 50 }
424
+ ]),
425
+ executables: Object.freeze(["c++", "g++", "clang++", "cmake", "make"])
426
+ };
427
+ }
428
+ function swiftProfile() {
429
+ return {
430
+ id: "swift",
431
+ displayName: "Swift",
432
+ extensions: Object.freeze([".swift"]),
433
+ lspLanguageIds: Object.freeze(["swift"]),
434
+ detectors: Object.freeze([{ kind: "manifest", filename: "Package.swift", weight: 90 }]),
435
+ ignoredDirectories: IGNORES,
436
+ packageManagers: Object.freeze(["swift"]),
437
+ executables: Object.freeze(["swift", "swiftc"]),
438
+ operations: Object.freeze({
439
+ semantic: async (ctx) => processPlan(ctx, "semantic", "swift", ["build"], {
440
+ parser: "swift",
441
+ reason: "Build Swift package to collect compiler diagnostics.",
442
+ mutating: true,
443
+ executesProjectCode: true
444
+ }),
445
+ test: async (ctx) => processPlan(ctx, "test", "swift", ["test"], {
446
+ parser: "swift-test",
447
+ reason: "Run Swift tests.",
448
+ mutating: true,
449
+ executesProjectCode: true
450
+ }),
451
+ build: async (ctx) => processPlan(ctx, "build", "swift", ["build", "-c", "release"], {
452
+ parser: "swift",
453
+ reason: "Build the Swift package in release mode.",
454
+ mutating: true,
455
+ executesProjectCode: true
456
+ }),
457
+ run: async (ctx) => processPlan(ctx, "run", "swift", ["run"], {
458
+ parser: "command-text",
459
+ reason: "Run the Swift package entry point.",
460
+ executesProjectCode: true
461
+ })
462
+ })
463
+ };
464
+ }
465
+ function dartProfile() {
466
+ return {
467
+ id: "dart",
468
+ displayName: "Dart / Flutter",
469
+ extensions: Object.freeze([".dart"]),
470
+ lspLanguageIds: Object.freeze(["dart"]),
471
+ detectors: Object.freeze([
472
+ { kind: "manifest", filename: "pubspec.yaml", weight: 90 },
473
+ { kind: "lockfile", filename: "pubspec.lock", weight: 30 }
474
+ ]),
475
+ ignoredDirectories: IGNORES,
476
+ packageManagers: Object.freeze(["pub"]),
477
+ executables: Object.freeze(["dart", "flutter"]),
478
+ operations: Object.freeze({
479
+ semantic: async (ctx) => processPlan(ctx, "semantic", "dart", ["analyze"], {
480
+ parser: "dart-analyze",
481
+ reason: "Run Dart static analysis."
482
+ }),
483
+ "format-check": async (ctx) => processPlan(
484
+ ctx,
485
+ "format-check",
486
+ "dart",
487
+ ["format", "--output=none", "--set-exit-if-changed", "."],
488
+ {
489
+ parser: "dart-format",
490
+ reason: "Check Dart formatting without writing files."
491
+ }
492
+ ),
493
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "dart", ["format", "."], {
494
+ parser: "dart-format",
495
+ reason: "Format Dart source files.",
496
+ mutating: true
497
+ }),
498
+ test: async (ctx) => processPlan(ctx, "test", "dart", ["test"], {
499
+ parser: "dart-test",
500
+ reason: "Run Dart tests.",
501
+ mutating: true,
502
+ executesProjectCode: true
503
+ }),
504
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "dart", ["pub", "get"], {
505
+ parser: "package-text",
506
+ reason: "Fetch Dart package dependencies.",
507
+ mutating: true,
508
+ network: true
509
+ }),
510
+ "package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "dart", ["pub", "outdated"], {
511
+ parser: "dart-outdated",
512
+ reason: "Check for outdated Dart packages.",
513
+ network: true
514
+ }),
515
+ run: async (ctx) => processPlan(ctx, "run", "dart", ["run"], {
516
+ parser: "command-text",
517
+ reason: "Run the Dart / Flutter project entry point.",
518
+ executesProjectCode: true
519
+ })
520
+ })
521
+ };
522
+ }
523
+ function denoProfile() {
524
+ return {
525
+ id: "deno",
526
+ displayName: "Deno",
527
+ extensions: Object.freeze([".ts", ".tsx", ".js", ".jsx"]),
528
+ lspLanguageIds: Object.freeze(["typescript", "javascript"]),
529
+ detectors: Object.freeze([
530
+ { kind: "config", filename: "deno.json", weight: 90 },
531
+ { kind: "config", filename: "deno.jsonc", weight: 90 },
532
+ { kind: "config", filename: "import_map.json", weight: 60 }
533
+ ]),
534
+ ignoredDirectories: IGNORES,
535
+ packageManagers: Object.freeze([]),
536
+ executables: Object.freeze(["deno"]),
537
+ // .ts/.js extensions are shared with the TypeScript/JavaScript profiles;
538
+ // only a deno.json(c)/import_map.json detector hit may establish a workspace.
539
+ sourceFallback: false,
540
+ operations: Object.freeze({
541
+ test: async (ctx) => processPlan(ctx, "test", "deno", ["test"], {
542
+ parser: "deno-test",
543
+ reason: "Run Deno tests.",
544
+ mutating: true,
545
+ executesProjectCode: true
546
+ }),
547
+ run: async (ctx) => processPlan(ctx, "run", "deno", ["run", "--allow-all", "main.ts"], {
548
+ parser: "command-text",
549
+ reason: "Run the Deno project entry point with full permission.",
550
+ executesProjectCode: true,
551
+ mutating: true
552
+ })
553
+ })
554
+ };
555
+ }
556
+ function elixirProfile() {
557
+ return {
558
+ id: "elixir",
559
+ displayName: "Elixir",
560
+ extensions: Object.freeze([".ex", ".exs"]),
561
+ lspLanguageIds: Object.freeze(["elixir"]),
562
+ detectors: Object.freeze([
563
+ { kind: "manifest", filename: "mix.exs", weight: 90 },
564
+ { kind: "lockfile", filename: "mix.lock", weight: 30 }
565
+ ]),
566
+ ignoredDirectories: IGNORES,
567
+ packageManagers: Object.freeze(["mix"]),
568
+ executables: Object.freeze(["mix", "elixir"]),
569
+ operations: Object.freeze({
570
+ semantic: async (ctx) => unavailable(ctx, "semantic", "Elixir compilation is handled by the build operation."),
571
+ lint: async (ctx) => unavailable(ctx, "lint", "Elixir linting requires the credo package."),
572
+ test: async (ctx) => processPlan(ctx, "test", "mix", ["test"], {
573
+ parser: "mix-test",
574
+ reason: "Run ExUnit tests.",
575
+ mutating: true,
576
+ executesProjectCode: true
577
+ }),
578
+ build: async (ctx) => processPlan(ctx, "build", "mix", ["compile"], {
579
+ parser: "mix",
580
+ reason: "Compile the Mix project.",
581
+ mutating: true,
582
+ executesProjectCode: true
583
+ }),
584
+ run: async (ctx) => processPlan(ctx, "run", "mix", ["run"], {
585
+ parser: "command-text",
586
+ reason: "Run the Mix project entry point.",
587
+ executesProjectCode: true
588
+ }),
589
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "mix", ["deps.get"], {
590
+ parser: "package-text",
591
+ reason: "Fetch Elixir dependencies.",
592
+ mutating: true,
593
+ network: true
594
+ })
595
+ })
596
+ };
597
+ }
598
+ function shellProfile() {
599
+ return {
600
+ id: "shell",
601
+ displayName: "Shell",
602
+ extensions: Object.freeze([".sh", ".bash"]),
603
+ lspLanguageIds: Object.freeze(["shellscript"]),
604
+ detectors: Object.freeze([{ kind: "config", filename: "ShellCheckrc", weight: 50 }]),
605
+ ignoredDirectories: IGNORES,
606
+ packageManagers: Object.freeze([]),
607
+ executables: Object.freeze(["bash", "sh", "shellcheck", "shfmt"]),
608
+ operations: Object.freeze({
609
+ syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "bash", ["-n", ctx.options.target], {
610
+ parser: "shell",
611
+ reason: "Check the shell script for syntax errors."
612
+ }) : unavailable(ctx, "syntax", "Shell syntax check requires an explicit target file."),
613
+ lint: async (ctx) => ctx.options.target ? processPlan(ctx, "lint", "shellcheck", ["--format=json", ctx.options.target], {
614
+ parser: "shellcheck",
615
+ reason: "Run ShellCheck on the target script."
616
+ }) : unavailable(ctx, "lint", "Shell linting requires an explicit target file."),
617
+ "format-check": async (ctx) => ctx.options.target ? processPlan(ctx, "format-check", "shfmt", ["-d", ctx.options.target], {
618
+ parser: "shell",
619
+ reason: "Check shell formatting without writing."
620
+ }) : unavailable(
621
+ ctx,
622
+ "format-check",
623
+ "Shell formatting check requires an explicit target file."
624
+ ),
625
+ "format-write": async (ctx) => ctx.options.target ? processPlan(ctx, "format-write", "shfmt", ["-w", ctx.options.target], {
626
+ parser: "shell",
627
+ reason: "Format the shell script.",
628
+ mutating: true
629
+ }) : unavailable(ctx, "format-write", "Shell formatting requires an explicit target file.")
630
+ })
631
+ };
632
+ }
633
+ var ADDITIONAL_LANGUAGE_PROFILES = Object.freeze([
634
+ pythonProfile(),
635
+ javaProfile(),
636
+ rubyProfile(),
637
+ cProfile(),
638
+ cppProfile(),
639
+ swiftProfile(),
640
+ dartProfile(),
641
+ denoProfile(),
642
+ elixirProfile(),
643
+ shellProfile()
644
+ ]);
645
+
646
+ // src/languages/profiles/primary.ts
647
+ var COMMON_IGNORES = Object.freeze([
648
+ ".git",
649
+ ".wrongstack",
650
+ "node_modules",
651
+ "vendor",
652
+ "target",
653
+ "bin",
654
+ "obj",
655
+ "dist",
656
+ "build",
657
+ "coverage"
658
+ ]);
659
+ function nodeManager(ctx) {
660
+ const lockfiles = new Set(
661
+ ctx.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
662
+ );
663
+ if (lockfiles.size > 1 && !ctx.workspace.packageManager) return void 0;
664
+ return ctx.workspace.packageManager ?? "npm";
665
+ }
666
+ function scriptPlan(ctx, operation, script) {
667
+ const manager = nodeManager(ctx);
668
+ if (!manager)
669
+ return unavailable(
670
+ ctx,
671
+ operation,
672
+ "Conflicting Node lockfiles make the package manager ambiguous."
673
+ );
674
+ const args = manager === "npm" ? ["run", script] : [script];
675
+ return processPlan(ctx, operation, manager, args, {
676
+ parser: "command-text",
677
+ reason: `Run the detected ${manager} ${script} script for this workspace.`,
678
+ mutating: true,
679
+ executesProjectCode: true
680
+ });
681
+ }
682
+ function nodeExec(ctx, executable, args) {
683
+ const manager = ctx.workspace.packageManager ?? "npm";
684
+ if (manager === "pnpm") return { command: "pnpm", args: ["exec", executable, ...args] };
685
+ if (manager === "yarn") return { command: "yarn", args: ["exec", executable, ...args] };
686
+ if (manager === "bun") return { command: "bun", args: ["x", executable, ...args] };
687
+ return { command: "npx", args: ["--no-install", executable, ...args] };
688
+ }
689
+ function typescriptProfile() {
690
+ return {
691
+ id: "typescript",
692
+ displayName: "TypeScript",
693
+ extensions: Object.freeze([".ts", ".tsx", ".mts", ".cts"]),
694
+ lspLanguageIds: Object.freeze(["typescript", "typescriptreact"]),
695
+ detectors: Object.freeze([
696
+ { kind: "config", filename: "tsconfig.json", weight: 90 },
697
+ { kind: "manifest", filename: "package.json", weight: 55 },
698
+ { kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
699
+ { kind: "lockfile", filename: "yarn.lock", weight: 30 },
700
+ { kind: "lockfile", filename: "package-lock.json", weight: 30 },
701
+ { kind: "lockfile", filename: "bun.lock", weight: 30 },
702
+ { kind: "lockfile", filename: "bun.lockb", weight: 30 }
703
+ ]),
704
+ ignoredDirectories: COMMON_IGNORES,
705
+ packageManagers: Object.freeze(["pnpm", "yarn", "bun", "npm"]),
706
+ executables: Object.freeze(["pnpm", "yarn", "bun", "npx", "npm"]),
707
+ operations: Object.freeze({
708
+ syntax: async (ctx) => internalPlan(
709
+ ctx,
710
+ "syntax",
711
+ "typescript-parser",
712
+ "Parse the target with the TypeScript compiler API."
713
+ ),
714
+ semantic: async (ctx) => {
715
+ const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
716
+ return processPlan(ctx, "semantic", run.command, run.args, {
717
+ parser: "typescript",
718
+ reason: "Run the workspace TypeScript compiler without emitting files.",
719
+ executesProjectCode: true
720
+ });
721
+ },
722
+ lint: async (ctx) => {
723
+ const run = nodeExec(ctx, "biome", ["lint", "."]);
724
+ return processPlan(ctx, "lint", run.command, run.args, {
725
+ parser: "biome",
726
+ reason: "Run the project-local Biome linter.",
727
+ executesProjectCode: true
728
+ });
729
+ },
730
+ "format-check": async (ctx) => {
731
+ const run = nodeExec(ctx, "biome", ["format", "--check", "."]);
732
+ return processPlan(ctx, "format-check", run.command, run.args, {
733
+ parser: "biome",
734
+ reason: "Check formatting with the project-local Biome formatter.",
735
+ executesProjectCode: true
736
+ });
737
+ },
738
+ "format-write": async (ctx) => {
739
+ const run = nodeExec(ctx, "biome", ["format", "--write", "."]);
740
+ return processPlan(ctx, "format-write", run.command, run.args, {
741
+ parser: "biome",
742
+ reason: "Format the workspace with the project-local Biome formatter.",
743
+ mutating: true,
744
+ executesProjectCode: true
745
+ });
746
+ },
747
+ test: async (ctx) => ctx.options.filter || ctx.options.coverage ? unavailable(
748
+ ctx,
749
+ "test",
750
+ "The detected package script does not expose deterministic filter or coverage adapters."
751
+ ) : scriptPlan(ctx, "test", "test"),
752
+ build: async (ctx) => scriptPlan(ctx, "build", "build"),
753
+ run: async (ctx) => scriptPlan(ctx, "run", "dev"),
754
+ "debug-compile": async (ctx) => {
755
+ const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
756
+ return processPlan(ctx, "debug-compile", run.command, run.args, {
757
+ parser: "typescript",
758
+ reason: "Collect deterministic TypeScript compiler diagnostics.",
759
+ executesProjectCode: true
760
+ });
761
+ },
762
+ "package-install": async (ctx) => {
763
+ const manager = ctx.workspace.packageManager ?? "npm";
764
+ const args = manager === "yarn" ? ["install", "--ignore-scripts"] : ["install", "--ignore-scripts"];
765
+ return processPlan(ctx, "package-install", manager, args, {
766
+ parser: "package-text",
767
+ reason: `Restore declared dependencies with ${manager} and lifecycle scripts disabled.`,
768
+ mutating: true,
769
+ network: true,
770
+ executesProjectCode: false
771
+ });
772
+ },
773
+ "package-add": async (ctx) => {
774
+ const names = packageNames(ctx);
775
+ if (names.length === 0)
776
+ return unavailable(ctx, "package-add", "At least one package name is required.");
777
+ const manager = ctx.workspace.packageManager ?? "npm";
778
+ const args = manager === "npm" ? ["install", "--ignore-scripts", ...names] : ["add", "--ignore-scripts", ...names];
779
+ return processPlan(ctx, "package-add", manager, args, {
780
+ parser: "package-text",
781
+ reason: `Add validated packages with ${manager} and lifecycle scripts disabled.`,
782
+ mutating: true,
783
+ network: true
784
+ });
785
+ },
786
+ "package-remove": async (ctx) => {
787
+ const names = packageNames(ctx);
788
+ if (names.length === 0)
789
+ return unavailable(ctx, "package-remove", "At least one package name is required.");
790
+ const manager = ctx.workspace.packageManager ?? "npm";
791
+ const args = manager === "npm" ? ["uninstall", "--ignore-scripts", ...names] : ["remove", "--ignore-scripts", ...names];
792
+ return processPlan(ctx, "package-remove", manager, args, {
793
+ parser: "package-text",
794
+ reason: `Remove validated packages with ${manager} and lifecycle scripts disabled.`,
795
+ mutating: true,
796
+ network: true
797
+ });
798
+ },
799
+ "package-audit": async (ctx) => {
800
+ const manager = ctx.workspace.packageManager ?? "npm";
801
+ return processPlan(ctx, "package-audit", manager, ["audit", "--json"], {
802
+ parser: "npm-audit",
803
+ reason: `Audit dependencies with the detected ${manager} package manager.`,
804
+ network: true
805
+ });
806
+ },
807
+ "package-outdated": async (ctx) => {
808
+ const manager = ctx.workspace.packageManager ?? "npm";
809
+ return processPlan(ctx, "package-outdated", manager, ["outdated", "--json"], {
810
+ parser: "npm-outdated",
811
+ reason: `Check outdated dependencies with ${manager}.`,
812
+ network: true
813
+ });
814
+ }
815
+ })
816
+ };
817
+ }
818
+ function javascriptProfile() {
819
+ const ts = typescriptProfile();
820
+ return {
821
+ ...ts,
822
+ id: "javascript",
823
+ displayName: "JavaScript",
824
+ extensions: Object.freeze([".js", ".jsx", ".mjs", ".cjs"]),
825
+ lspLanguageIds: Object.freeze(["javascript", "javascriptreact"]),
826
+ detectors: Object.freeze([
827
+ { kind: "config", filename: "jsconfig.json", weight: 90 },
828
+ { kind: "manifest", filename: "package.json", weight: 70 },
829
+ { kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
830
+ { kind: "lockfile", filename: "yarn.lock", weight: 30 },
831
+ { kind: "lockfile", filename: "package-lock.json", weight: 30 },
832
+ { kind: "lockfile", filename: "bun.lock", weight: 30 },
833
+ { kind: "lockfile", filename: "bun.lockb", weight: 30 }
834
+ ]),
835
+ operations: Object.freeze({
836
+ ...ts.operations,
837
+ syntax: async (ctx) => internalPlan(
838
+ ctx,
839
+ "syntax",
840
+ "typescript-parser",
841
+ "Parse JavaScript with the TypeScript compiler API."
842
+ )
843
+ })
844
+ };
845
+ }
846
+ var goProfile = {
847
+ id: "go",
848
+ displayName: "Go",
849
+ extensions: Object.freeze([".go"]),
850
+ lspLanguageIds: Object.freeze(["go"]),
851
+ detectors: Object.freeze([
852
+ { kind: "manifest", filename: "go.mod", weight: 90 },
853
+ { kind: "manifest", filename: "go.work", weight: 95 },
854
+ { kind: "lockfile", filename: "go.sum", weight: 30 }
855
+ ]),
856
+ ignoredDirectories: COMMON_IGNORES,
857
+ packageManagers: Object.freeze(["go"]),
858
+ executables: Object.freeze(["go", "gofmt"]),
859
+ operations: Object.freeze({
860
+ syntax: async (ctx) => {
861
+ if (!ctx.target)
862
+ return unavailable(ctx, "syntax", "Go syntax planning requires a target file.");
863
+ return processPlan(ctx, "syntax", "gofmt", ["-e", "-d", ctx.target], {
864
+ parser: "gofmt",
865
+ reason: "Parse the target and report syntax errors without writing it."
866
+ });
867
+ },
868
+ semantic: async (ctx) => processPlan(ctx, "semantic", "go", ["test", "-run", "^$", "./..."], {
869
+ parser: "go-test",
870
+ reason: "Compile all Go packages without selecting tests.",
871
+ mutating: true,
872
+ executesProjectCode: true
873
+ }),
874
+ lint: async (ctx) => processPlan(ctx, "lint", "go", ["vet", "./..."], {
875
+ parser: "go-compiler",
876
+ reason: "Run the standard Go vet checks.",
877
+ mutating: true,
878
+ executesProjectCode: true
879
+ }),
880
+ "format-check": async (ctx) => ctx.target ? processPlan(ctx, "format-check", "gofmt", ["-d", ctx.target], {
881
+ parser: "gofmt",
882
+ reason: "Report Go formatting differences for the target without writing it."
883
+ }) : unavailable(ctx, "format-check", "Go formatting requires an explicit target file."),
884
+ "format-write": async (ctx) => ctx.target ? processPlan(ctx, "format-write", "gofmt", ["-w", ctx.target], {
885
+ parser: "gofmt",
886
+ reason: "Format the target Go source file.",
887
+ mutating: true
888
+ }) : unavailable(ctx, "format-write", "Go formatting requires an explicit target file."),
889
+ test: async (ctx) => processPlan(
890
+ ctx,
891
+ "test",
892
+ "go",
893
+ ["test", ...ctx.options.filter ? ["-run", ctx.options.filter] : [], "./..."],
894
+ {
895
+ parser: "go-test",
896
+ reason: ctx.options.filter ? "Run filtered Go tests." : "Run all Go tests.",
897
+ mutating: true,
898
+ executesProjectCode: true
899
+ }
900
+ ),
901
+ build: async (ctx) => processPlan(ctx, "build", "go", ["build", "./..."], {
902
+ parser: "go-compiler",
903
+ reason: "Build all Go packages.",
904
+ mutating: true,
905
+ executesProjectCode: true
906
+ }),
907
+ run: async (ctx) => processPlan(ctx, "run", "go", ["run", "."], {
908
+ parser: "command-text",
909
+ reason: "Run the Go module entry point.",
910
+ executesProjectCode: true
911
+ }),
912
+ "debug-race": async (ctx) => processPlan(ctx, "debug-race", "go", ["test", "-race", "./..."], {
913
+ parser: "go-test",
914
+ reason: "Collect Go race-detector evidence.",
915
+ mutating: true,
916
+ executesProjectCode: true
917
+ }),
918
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "go", ["mod", "download"], {
919
+ parser: "go-module",
920
+ reason: "Download the dependencies declared by go.mod.",
921
+ mutating: true,
922
+ network: true
923
+ }),
924
+ "package-add": async (ctx) => {
925
+ const names = packageNames(ctx);
926
+ if (names.length === 0)
927
+ return unavailable(ctx, "package-add", "At least one Go module is required.");
928
+ return processPlan(ctx, "package-add", "go", ["get", ...names], {
929
+ parser: "go-module",
930
+ reason: "Add validated Go modules.",
931
+ mutating: true,
932
+ network: true
933
+ });
934
+ },
935
+ "package-update": async (ctx) => processPlan(ctx, "package-update", "go", ["get", "-u", "./..."], {
936
+ parser: "go-module",
937
+ reason: "Update dependencies of all Go packages.",
938
+ mutating: true,
939
+ network: true
940
+ })
941
+ })
942
+ };
943
+ var rustProfile = {
944
+ id: "rust",
945
+ displayName: "Rust",
946
+ extensions: Object.freeze([".rs"]),
947
+ lspLanguageIds: Object.freeze(["rust"]),
948
+ detectors: Object.freeze([
949
+ { kind: "manifest", filename: "Cargo.toml", weight: 90 },
950
+ { kind: "lockfile", filename: "Cargo.lock", weight: 30 }
951
+ ]),
952
+ ignoredDirectories: COMMON_IGNORES,
953
+ packageManagers: Object.freeze(["cargo"]),
954
+ executables: Object.freeze(["cargo"]),
955
+ operations: Object.freeze({
956
+ syntax: async (ctx) => processPlan(ctx, "syntax", "cargo", ["check", "--message-format=json"], {
957
+ parser: "cargo-json",
958
+ reason: "Check Rust syntax and semantics with Cargo.",
959
+ mutating: true,
960
+ executesProjectCode: true
961
+ }),
962
+ semantic: async (ctx) => processPlan(ctx, "semantic", "cargo", ["check", "--message-format=json"], {
963
+ parser: "cargo-json",
964
+ reason: "Collect Rust compiler diagnostics with Cargo check.",
965
+ mutating: true,
966
+ executesProjectCode: true
967
+ }),
968
+ lint: async (ctx) => processPlan(ctx, "lint", "cargo", ["clippy", "--message-format=json"], {
969
+ parser: "cargo-json",
970
+ reason: "Run Clippy for this crate.",
971
+ mutating: true,
972
+ executesProjectCode: true
973
+ }),
974
+ "format-check": async (ctx) => processPlan(ctx, "format-check", "cargo", ["fmt", "--check"], {
975
+ parser: "cargo-fmt",
976
+ reason: "Check Rust formatting without writing files."
977
+ }),
978
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "cargo", ["fmt"], {
979
+ parser: "cargo-fmt",
980
+ reason: "Format Rust source files in the workspace.",
981
+ mutating: true
982
+ }),
983
+ "test-compile": async (ctx) => processPlan(ctx, "test-compile", "cargo", ["test", "--no-run", "--message-format=json"], {
984
+ parser: "cargo-json",
985
+ reason: "Compile Rust tests without running them.",
986
+ mutating: true,
987
+ executesProjectCode: true
988
+ }),
989
+ test: async (ctx) => processPlan(
990
+ ctx,
991
+ "test",
992
+ "cargo",
993
+ ["test", ...ctx.options.filter ? [ctx.options.filter] : []],
994
+ {
995
+ parser: "cargo-test",
996
+ reason: ctx.options.filter ? "Run filtered Rust tests." : "Run Rust tests.",
997
+ mutating: true,
998
+ executesProjectCode: true
999
+ }
1000
+ ),
1001
+ build: async (ctx) => processPlan(ctx, "build", "cargo", ["build", "--message-format=json"], {
1002
+ parser: "cargo-json",
1003
+ reason: "Build the Rust workspace.",
1004
+ mutating: true,
1005
+ executesProjectCode: true
1006
+ }),
1007
+ run: async (ctx) => processPlan(ctx, "run", "cargo", ["run"], {
1008
+ parser: "command-text",
1009
+ reason: "Run the Rust workspace entry point.",
1010
+ executesProjectCode: true
1011
+ }),
1012
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "cargo", ["fetch", "--locked"], {
1013
+ parser: "cargo-json",
1014
+ reason: "Fetch locked Rust dependencies.",
1015
+ mutating: true,
1016
+ network: true
1017
+ }),
1018
+ "package-add": async (ctx) => {
1019
+ const names = packageNames(ctx);
1020
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one crate is required.") : processPlan(ctx, "package-add", "cargo", ["add", ...names], {
1021
+ parser: "cargo-text",
1022
+ reason: "Add validated Rust crates.",
1023
+ mutating: true,
1024
+ network: true
1025
+ });
1026
+ },
1027
+ "package-remove": async (ctx) => {
1028
+ const names = packageNames(ctx);
1029
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one crate is required.") : processPlan(ctx, "package-remove", "cargo", ["remove", ...names], {
1030
+ parser: "cargo-text",
1031
+ reason: "Remove validated Rust crates.",
1032
+ mutating: true
1033
+ });
1034
+ },
1035
+ "package-update": async (ctx) => processPlan(ctx, "package-update", "cargo", ["update"], {
1036
+ parser: "cargo-text",
1037
+ reason: "Update the Cargo lockfile.",
1038
+ mutating: true,
1039
+ network: true
1040
+ }),
1041
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "cargo", ["audit", "--json"], {
1042
+ parser: "cargo-audit",
1043
+ reason: "Audit Rust dependencies when cargo-audit is installed.",
1044
+ network: true
1045
+ })
1046
+ })
1047
+ };
1048
+ var phpProfile = {
1049
+ id: "php",
1050
+ displayName: "PHP",
1051
+ extensions: Object.freeze([".php"]),
1052
+ lspLanguageIds: Object.freeze(["php"]),
1053
+ detectors: Object.freeze([
1054
+ { kind: "manifest", filename: "composer.json", weight: 90 },
1055
+ { kind: "lockfile", filename: "composer.lock", weight: 30 },
1056
+ { kind: "config", filename: "phpunit.xml", weight: 25 },
1057
+ { kind: "config", filename: "phpunit.xml.dist", weight: 25 }
1058
+ ]),
1059
+ ignoredDirectories: COMMON_IGNORES,
1060
+ packageManagers: Object.freeze(["composer"]),
1061
+ executables: Object.freeze(["php", "composer"]),
1062
+ operations: Object.freeze({
1063
+ syntax: async (ctx) => ctx.target ? processPlan(ctx, "syntax", "php", ["-l", ctx.target], {
1064
+ parser: "php-lint",
1065
+ reason: "Lint the target PHP file without executing it."
1066
+ }) : unavailable(ctx, "syntax", "PHP syntax planning requires a target file."),
1067
+ semantic: async (ctx) => unavailable(
1068
+ ctx,
1069
+ "semantic",
1070
+ "No configured PHPStan or Psalm adapter was detected in Phase 1."
1071
+ ),
1072
+ test: async (ctx) => processPlan(
1073
+ ctx,
1074
+ "test",
1075
+ "php",
1076
+ ["vendor/bin/phpunit", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
1077
+ {
1078
+ parser: "phpunit",
1079
+ reason: ctx.options.filter ? "Run filtered tests with the project-local PHPUnit runner." : "Run the project-local PHPUnit test runner.",
1080
+ executesProjectCode: true
1081
+ }
1082
+ ),
1083
+ "package-install": async (ctx) => processPlan(
1084
+ ctx,
1085
+ "package-install",
1086
+ "composer",
1087
+ ["install", "--no-interaction", "--no-scripts"],
1088
+ {
1089
+ parser: "composer",
1090
+ reason: "Restore Composer dependencies without scripts.",
1091
+ mutating: true,
1092
+ network: true
1093
+ }
1094
+ ),
1095
+ "package-add": async (ctx) => {
1096
+ const names = packageNames(ctx);
1097
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one Composer package is required.") : processPlan(
1098
+ ctx,
1099
+ "package-add",
1100
+ "composer",
1101
+ ["require", "--no-interaction", "--no-scripts", ...names],
1102
+ {
1103
+ parser: "composer",
1104
+ reason: "Add validated Composer packages without scripts.",
1105
+ mutating: true,
1106
+ network: true
1107
+ }
1108
+ );
1109
+ },
1110
+ "package-remove": async (ctx) => {
1111
+ const names = packageNames(ctx);
1112
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one Composer package is required.") : processPlan(
1113
+ ctx,
1114
+ "package-remove",
1115
+ "composer",
1116
+ ["remove", "--no-interaction", "--no-scripts", ...names],
1117
+ {
1118
+ parser: "composer",
1119
+ reason: "Remove validated Composer packages without scripts.",
1120
+ mutating: true
1121
+ }
1122
+ );
1123
+ },
1124
+ "package-update": async (ctx) => processPlan(
1125
+ ctx,
1126
+ "package-update",
1127
+ "composer",
1128
+ ["update", "--no-interaction", "--no-scripts"],
1129
+ {
1130
+ parser: "composer",
1131
+ reason: "Update Composer dependencies without scripts.",
1132
+ mutating: true,
1133
+ network: true
1134
+ }
1135
+ ),
1136
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "composer", ["audit", "--format=json"], {
1137
+ parser: "composer-audit",
1138
+ reason: "Audit Composer dependencies.",
1139
+ network: true
1140
+ }),
1141
+ "package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "composer", ["outdated", "--format=json"], {
1142
+ parser: "composer-outdated",
1143
+ reason: "Check outdated Composer dependencies.",
1144
+ network: true
1145
+ })
1146
+ })
1147
+ };
1148
+ var csharpProfile = {
1149
+ id: "csharp",
1150
+ displayName: "C# / .NET",
1151
+ extensions: Object.freeze([".cs"]),
1152
+ lspLanguageIds: Object.freeze(["csharp"]),
1153
+ detectors: Object.freeze([
1154
+ { kind: "config", filename: "global.json", weight: 25 },
1155
+ { kind: "manifest", suffix: ".slnx", weight: 95 },
1156
+ { kind: "manifest", suffix: ".sln", weight: 95 },
1157
+ { kind: "manifest", suffix: ".csproj", weight: 90 },
1158
+ { kind: "manifest", suffix: ".fsproj", weight: 90 },
1159
+ { kind: "lockfile", filename: "packages.lock.json", weight: 30 }
1160
+ ]),
1161
+ ignoredDirectories: COMMON_IGNORES,
1162
+ packageManagers: Object.freeze(["dotnet"]),
1163
+ executables: Object.freeze(["dotnet"]),
1164
+ operations: Object.freeze({
1165
+ syntax: async (ctx) => processPlan(ctx, "syntax", "dotnet", ["build", "--no-restore"], {
1166
+ parser: "dotnet-build",
1167
+ reason: "Use the nearest project or solution to collect C# syntax diagnostics.",
1168
+ mutating: true,
1169
+ executesProjectCode: true
1170
+ }),
1171
+ semantic: async (ctx) => processPlan(ctx, "semantic", "dotnet", ["build", "--no-restore"], {
1172
+ parser: "dotnet-build",
1173
+ reason: "Build without restoring to collect .NET compiler diagnostics.",
1174
+ mutating: true,
1175
+ executesProjectCode: true
1176
+ }),
1177
+ lint: async (ctx) => processPlan(ctx, "lint", "dotnet", ["format", "--verify-no-changes", "--no-restore"], {
1178
+ parser: "dotnet-format",
1179
+ reason: "Verify .NET formatting and analyzers without writing source files.",
1180
+ mutating: true,
1181
+ executesProjectCode: true
1182
+ }),
1183
+ "format-check": async (ctx) => processPlan(
1184
+ ctx,
1185
+ "format-check",
1186
+ "dotnet",
1187
+ ["format", "--verify-no-changes", "--no-restore"],
1188
+ {
1189
+ parser: "dotnet-format",
1190
+ reason: "Verify .NET formatting without source writes.",
1191
+ mutating: true,
1192
+ executesProjectCode: true
1193
+ }
1194
+ ),
1195
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "dotnet", ["format", "--no-restore"], {
1196
+ parser: "dotnet-format",
1197
+ reason: "Format .NET source files without restoring packages.",
1198
+ mutating: true,
1199
+ executesProjectCode: true
1200
+ }),
1201
+ test: async (ctx) => processPlan(
1202
+ ctx,
1203
+ "test",
1204
+ "dotnet",
1205
+ ["test", "--no-restore", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
1206
+ {
1207
+ parser: "dotnet-test",
1208
+ reason: ctx.options.filter ? "Run filtered .NET tests without restoring packages." : "Run .NET tests without restoring packages.",
1209
+ mutating: true,
1210
+ executesProjectCode: true
1211
+ }
1212
+ ),
1213
+ build: async (ctx) => processPlan(ctx, "build", "dotnet", ["build", "--no-restore"], {
1214
+ parser: "dotnet-build",
1215
+ reason: "Build the nearest .NET project or solution without restoring packages.",
1216
+ mutating: true,
1217
+ executesProjectCode: true
1218
+ }),
1219
+ run: async (ctx) => processPlan(ctx, "run", "dotnet", ["run", "--no-restore"], {
1220
+ parser: "command-text",
1221
+ reason: "Run the .NET project entry point.",
1222
+ mutating: true,
1223
+ executesProjectCode: true
1224
+ }),
1225
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "dotnet", ["restore", "--locked-mode"], {
1226
+ parser: "dotnet-restore",
1227
+ reason: "Restore locked .NET dependencies.",
1228
+ mutating: true,
1229
+ network: true,
1230
+ executesProjectCode: true
1231
+ }),
1232
+ "package-add": async (ctx) => {
1233
+ const names = packageNames(ctx);
1234
+ const [spec] = names;
1235
+ const versionAt = spec?.lastIndexOf("@") ?? -1;
1236
+ const packageName = versionAt > 0 ? spec?.slice(0, versionAt) : spec;
1237
+ const packageVersion = versionAt > 0 ? spec?.slice(versionAt + 1) : void 0;
1238
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one NuGet package is required.") : names.length > 1 ? unavailable(ctx, "package-add", "NuGet package changes run one package at a time.") : processPlan(
1239
+ ctx,
1240
+ "package-add",
1241
+ "dotnet",
1242
+ [
1243
+ "add",
1244
+ "package",
1245
+ packageName ?? "",
1246
+ ...packageVersion ? ["--version", packageVersion] : []
1247
+ ],
1248
+ {
1249
+ parser: "dotnet-package",
1250
+ reason: "Add validated NuGet packages.",
1251
+ mutating: true,
1252
+ network: true,
1253
+ executesProjectCode: true
1254
+ }
1255
+ );
1256
+ },
1257
+ "package-remove": async (ctx) => {
1258
+ const names = packageNames(ctx);
1259
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one NuGet package is required.") : processPlan(ctx, "package-remove", "dotnet", ["remove", "package", ...names], {
1260
+ parser: "dotnet-package",
1261
+ reason: "Remove validated NuGet packages.",
1262
+ mutating: true
1263
+ });
1264
+ },
1265
+ "package-audit": async (ctx) => processPlan(
1266
+ ctx,
1267
+ "package-audit",
1268
+ "dotnet",
1269
+ ["list", "package", "--vulnerable", "--format", "json"],
1270
+ { parser: "dotnet-package", reason: "List vulnerable NuGet dependencies.", network: true }
1271
+ ),
1272
+ "package-outdated": async (ctx) => processPlan(
1273
+ ctx,
1274
+ "package-outdated",
1275
+ "dotnet",
1276
+ ["list", "package", "--outdated", "--format", "json"],
1277
+ { parser: "dotnet-package", reason: "List outdated NuGet dependencies.", network: true }
1278
+ )
1279
+ })
1280
+ };
1281
+ var PRIMARY_LANGUAGE_PROFILES = Object.freeze([
1282
+ Object.freeze(typescriptProfile()),
1283
+ Object.freeze(javascriptProfile()),
1284
+ Object.freeze(goProfile),
1285
+ Object.freeze(rustProfile),
1286
+ Object.freeze(phpProfile),
1287
+ Object.freeze(csharpProfile)
1288
+ ]);
1289
+
1290
+ // src/languages/registry.ts
1291
+ var PROFILE_ID_RE = /^[a-z][a-z0-9-]{0,63}$/;
1292
+ var EXECUTABLE_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
1293
+ var KNOWN_OPERATIONS = /* @__PURE__ */ new Set([
1294
+ "syntax",
1295
+ "semantic",
1296
+ "lint",
1297
+ "format-check",
1298
+ "format-write",
1299
+ "test-compile",
1300
+ "test",
1301
+ "build",
1302
+ "run",
1303
+ "debug-compile",
1304
+ "debug-test",
1305
+ "debug-runtime",
1306
+ "debug-race",
1307
+ "package-install",
1308
+ "package-add",
1309
+ "package-remove",
1310
+ "package-update",
1311
+ "package-audit",
1312
+ "package-outdated"
1313
+ ]);
1314
+ var LanguageProfileRegistry = class {
1315
+ #profiles = /* @__PURE__ */ new Map();
1316
+ constructor(profiles = []) {
1317
+ for (const profile of profiles) this.register(profile);
1318
+ }
1319
+ register(profile) {
1320
+ const errors = validateLanguageProfile(profile);
1321
+ if (errors.length > 0) {
1322
+ throw new Error(`Invalid language profile "${profile.id}": ${errors.join("; ")}`);
1323
+ }
1324
+ if (this.#profiles.has(profile.id)) {
1325
+ throw new Error(`Language profile "${profile.id}" is already registered`);
1326
+ }
1327
+ this.#profiles.set(profile.id, freezeProfile(profile));
1328
+ }
1329
+ get(id) {
1330
+ return this.#profiles.get(id);
1331
+ }
1332
+ list() {
1333
+ return Object.freeze([...this.#profiles.values()]);
1334
+ }
1335
+ };
1336
+ function validateLanguageProfile(profile) {
1337
+ const errors = [];
1338
+ if (!PROFILE_ID_RE.test(profile.id)) errors.push("id must be a lowercase stable identifier");
1339
+ if (!profile.displayName.trim()) errors.push("displayName is required");
1340
+ if (profile.extensions.length === 0) errors.push("at least one extension is required");
1341
+ for (const ext of profile.extensions) {
1342
+ if (!/^\.[a-z0-9+.-]+$/i.test(ext)) errors.push(`invalid extension "${ext}"`);
1343
+ }
1344
+ if (profile.detectors.length === 0) errors.push("at least one detector is required");
1345
+ for (const detector of profile.detectors) {
1346
+ if ((detector.filename ? 1 : 0) + (detector.suffix ? 1 : 0) !== 1) {
1347
+ errors.push("each detector must declare exactly one of filename or suffix");
1348
+ }
1349
+ if (!Number.isFinite(detector.weight) || detector.weight <= 0 || detector.weight > 100) {
1350
+ errors.push("detector weights must be in the range 1..100");
1351
+ }
1352
+ const marker = detector.filename ?? detector.suffix ?? "";
1353
+ if (marker.includes("/") || marker.includes("\\") || marker.includes("\0")) {
1354
+ errors.push(`detector marker "${marker}" must be a basename or suffix`);
1355
+ }
1356
+ }
1357
+ if (profile.executables.length === 0) errors.push("at least one executable is required");
1358
+ for (const executable of profile.executables) {
1359
+ if (!EXECUTABLE_RE.test(executable)) errors.push(`invalid executable token "${executable}"`);
1360
+ }
1361
+ for (const operation of Object.keys(profile.operations)) {
1362
+ if (!KNOWN_OPERATIONS.has(operation)) {
1363
+ errors.push(`unknown operation "${operation}"`);
1364
+ }
1365
+ if (typeof profile.operations[operation] !== "function") {
1366
+ errors.push(`operation "${operation}" must be a resolver function`);
1367
+ }
1368
+ }
1369
+ return [...new Set(errors)];
1370
+ }
1371
+ function freezeProfile(profile) {
1372
+ const detectors = Object.freeze(profile.detectors.map((item) => Object.freeze({ ...item })));
1373
+ const operations = Object.freeze({ ...profile.operations });
1374
+ return Object.freeze({
1375
+ ...profile,
1376
+ extensions: Object.freeze([...profile.extensions]),
1377
+ lspLanguageIds: Object.freeze([...profile.lspLanguageIds]),
1378
+ detectors,
1379
+ ignoredDirectories: Object.freeze([...profile.ignoredDirectories]),
1380
+ packageManagers: Object.freeze([...profile.packageManagers]),
1381
+ executables: Object.freeze([...profile.executables]),
1382
+ operations
1383
+ });
1384
+ }
1385
+ var languageProfileRegistry = new LanguageProfileRegistry([
1386
+ ...PRIMARY_LANGUAGE_PROFILES,
1387
+ ...ADDITIONAL_LANGUAGE_PROFILES
1388
+ ]);
1389
+
1390
+ // src/languages/detect.ts
1391
+ var DEFAULT_LIMITS = { maxDepth: 6, maxEntries: 5e3 };
1392
+ var SOURCE_WEIGHT = 5;
1393
+ var SOURCE_WEIGHT_CAP = 25;
1394
+ var TARGET_WEIGHT = 100;
1395
+ var GLOBAL_IGNORES = /* @__PURE__ */ new Set([
1396
+ ".git",
1397
+ ".wrongstack",
1398
+ "node_modules",
1399
+ "vendor",
1400
+ "target",
1401
+ "bin",
1402
+ "obj",
1403
+ "dist",
1404
+ "build",
1405
+ "out",
1406
+ "coverage",
1407
+ ".cache",
1408
+ ".idea",
1409
+ ".vscode",
1410
+ ".next",
1411
+ ".nuxt"
1412
+ ]);
1413
+ async function detectLanguageWorkspaces(options) {
1414
+ const projectRoot = await canonicalDirectory(options.projectRoot);
1415
+ const cwdInput = options.cwd ? path.isAbsolute(options.cwd) ? options.cwd : path.resolve(projectRoot, options.cwd) : projectRoot;
1416
+ const cwd = await canonicalInside(cwdInput, projectRoot, "cwd");
1417
+ const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
1418
+ const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
1419
+ const limits = normalizeLimits(options.limits);
1420
+ const extraIgnores = new Set(options.ignoredDirectories ?? []);
1421
+ const state = {
1422
+ entries: 0,
1423
+ truncated: false,
1424
+ sourcePaths: /* @__PURE__ */ new Map(),
1425
+ candidates: /* @__PURE__ */ new Map()
1426
+ };
1427
+ await scanDirectory(projectRoot, 0, profiles, limits, state, extraIgnores, options.signal);
1428
+ addSourceFallbacks(projectRoot, profiles, state);
1429
+ if (target) addTargetEvidence(target, projectRoot, profiles, state);
1430
+ const workspaces = await Promise.all(
1431
+ [...state.candidates.values()].map((candidate) => finalizeCandidate(candidate, projectRoot))
1432
+ );
1433
+ workspaces.sort(compareWorkspaces);
1434
+ return {
1435
+ projectRoot,
1436
+ scannedEntries: state.entries,
1437
+ truncated: state.truncated,
1438
+ workspaces
1439
+ };
1440
+ }
1441
+ async function scanDirectory(directory, depth, profiles, limits, state, extraIgnores, signal) {
1442
+ signal?.throwIfAborted();
1443
+ if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
1444
+ state.truncated = true;
1445
+ return;
1446
+ }
1447
+ let entries;
1448
+ try {
1449
+ entries = await fs.readdir(directory, { withFileTypes: true });
1450
+ } catch {
1451
+ return;
1452
+ }
1453
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1454
+ for (const entry of entries) {
1455
+ signal?.throwIfAborted();
1456
+ if (state.entries >= limits.maxEntries) {
1457
+ state.truncated = true;
1458
+ return;
1459
+ }
1460
+ state.entries++;
1461
+ const fullPath = path.join(directory, entry.name);
1462
+ if (entry.isSymbolicLink()) continue;
1463
+ if (entry.isDirectory()) {
1464
+ if (shouldIgnoreDirectory(entry.name, profiles, extraIgnores)) continue;
1465
+ if (depth >= limits.maxDepth) {
1466
+ state.truncated = true;
1467
+ continue;
1468
+ }
1469
+ await scanDirectory(fullPath, depth + 1, profiles, limits, state, extraIgnores, signal);
1470
+ continue;
1471
+ }
1472
+ if (!entry.isFile()) continue;
1473
+ collectFileEvidence(directory, fullPath, entry.name, profiles, state);
1474
+ }
1475
+ }
1476
+ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1477
+ const lower = basename4.toLowerCase();
1478
+ const extension = path.extname(lower);
1479
+ for (const profile of profiles) {
1480
+ const detector = profile.detectors.find(
1481
+ (rule) => rule.filename ? lower === rule.filename.toLowerCase() : lower.endsWith(rule.suffix.toLowerCase())
1482
+ );
1483
+ if (detector) {
1484
+ const candidate = getCandidate(state, profile, directory);
1485
+ candidate.evidence.push({
1486
+ kind: detector.kind,
1487
+ path: fullPath,
1488
+ value: basename4,
1489
+ weight: detector.weight
1490
+ });
1491
+ if (detector.kind === "manifest" || detector.kind === "config") {
1492
+ candidate.manifests.push(fullPath);
1493
+ }
1494
+ }
1495
+ if (profile.extensions.includes(extension)) {
1496
+ const paths = state.sourcePaths.get(profile.id) ?? [];
1497
+ if (paths.length < SOURCE_WEIGHT_CAP / SOURCE_WEIGHT) paths.push(fullPath);
1498
+ state.sourcePaths.set(profile.id, paths);
1499
+ }
1500
+ }
1501
+ }
1502
+ function addSourceFallbacks(projectRoot, profiles, state) {
1503
+ for (const profile of profiles) {
1504
+ const sources = state.sourcePaths.get(profile.id) ?? [];
1505
+ if (sources.length === 0) continue;
1506
+ const profileCandidates = [...state.candidates.values()].filter(
1507
+ (item) => item.profile.id === profile.id
1508
+ );
1509
+ for (const source of sources) {
1510
+ const containing = profileCandidates.filter((candidate2) => isInside(source, candidate2.root)).sort(
1511
+ (a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
1512
+ );
1513
+ const candidate = containing[0] ?? (profile.sourceFallback === false ? void 0 : getCandidate(state, profile, projectRoot));
1514
+ if (!candidate) continue;
1515
+ candidate.evidence.push({
1516
+ kind: "source",
1517
+ path: source,
1518
+ value: path.extname(source),
1519
+ weight: SOURCE_WEIGHT
1520
+ });
1521
+ }
1522
+ }
1523
+ }
1524
+ function addTargetEvidence(target, projectRoot, profiles, state) {
1525
+ const extension = path.extname(target).toLowerCase();
1526
+ for (const profile of profiles) {
1527
+ if (!profile.extensions.includes(extension)) continue;
1528
+ const candidates = [...state.candidates.values()].filter(
1529
+ (candidate2) => candidate2.profile.id === profile.id && isInside(target, candidate2.root)
1530
+ );
1531
+ const candidate = candidates.length > 0 ? candidates.sort(
1532
+ (a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
1533
+ )[0] : profile.sourceFallback === false ? void 0 : getCandidate(state, profile, path.dirname(target));
1534
+ if (!candidate) continue;
1535
+ candidate.evidence.push({
1536
+ kind: "target",
1537
+ path: target,
1538
+ value: extension,
1539
+ weight: TARGET_WEIGHT
1540
+ });
1541
+ }
1542
+ }
1543
+ async function finalizeCandidate(candidate, projectRoot) {
1544
+ const evidence = dedupeEvidence(candidate.evidence).sort(compareEvidence);
1545
+ const manifests = [...new Set(candidate.manifests)].sort();
1546
+ const confidence = Math.min(1, evidence.reduce((sum, item) => sum + item.weight, 0) / 100);
1547
+ const packageManager = await detectPackageManager(candidate.profile, candidate.root, evidence);
1548
+ const id = createHash("sha256").update(`${candidate.profile.id}\0${path.relative(projectRoot, candidate.root)}`).digest("hex").slice(0, 16);
1549
+ return Object.freeze({
1550
+ id,
1551
+ language: candidate.profile.id,
1552
+ root: candidate.root,
1553
+ confidence,
1554
+ evidence: Object.freeze(evidence.map((item) => Object.freeze(item))),
1555
+ ...packageManager ? { packageManager } : {},
1556
+ manifests: Object.freeze(manifests),
1557
+ capabilities: Object.freeze(
1558
+ Object.keys(candidate.profile.operations).sort()
1559
+ )
1560
+ });
1561
+ }
1562
+ async function detectPackageManager(profile, root, evidence) {
1563
+ if (profile.packageManagers.length === 1) return profile.packageManagers[0];
1564
+ if (profile.id !== "typescript" && profile.id !== "javascript") return void 0;
1565
+ let declared;
1566
+ try {
1567
+ const pkg = JSON.parse(await fs.readFile(path.join(root, "package.json"), "utf8"));
1568
+ if (typeof pkg.packageManager === "string") {
1569
+ const manager = pkg.packageManager.split("@")[0];
1570
+ if (manager && profile.packageManagers.includes(manager)) declared = manager;
1571
+ }
1572
+ } catch {
1573
+ }
1574
+ const lockManagers = /* @__PURE__ */ new Set();
1575
+ for (const item of evidence) {
1576
+ const name = path.basename(item.path).toLowerCase();
1577
+ if (name === "pnpm-lock.yaml") lockManagers.add("pnpm");
1578
+ else if (name === "yarn.lock") lockManagers.add("yarn");
1579
+ else if (name === "bun.lock" || name === "bun.lockb") lockManagers.add("bun");
1580
+ else if (name === "package-lock.json") lockManagers.add("npm");
1581
+ }
1582
+ if (declared && (lockManagers.size === 0 || lockManagers.size === 1 && lockManagers.has(declared))) {
1583
+ return declared;
1584
+ }
1585
+ if (lockManagers.size === 1) return [...lockManagers][0];
1586
+ if (lockManagers.size > 1) return void 0;
1587
+ return declared ?? "npm";
1588
+ }
1589
+ function getCandidate(state, profile, root) {
1590
+ const key = `${profile.id}\0${root}`;
1591
+ let candidate = state.candidates.get(key);
1592
+ if (!candidate) {
1593
+ candidate = { profile, root, evidence: [], manifests: [] };
1594
+ state.candidates.set(key, candidate);
1595
+ }
1596
+ return candidate;
1597
+ }
1598
+ function shouldIgnoreDirectory(name, profiles, extraIgnores) {
1599
+ if (GLOBAL_IGNORES.has(name) || extraIgnores.has(name) || name.startsWith(".")) return true;
1600
+ return profiles.some((profile) => profile.ignoredDirectories.includes(name));
1601
+ }
1602
+ function normalizeLimits(input) {
1603
+ const maxDepth = Math.max(
1604
+ 0,
1605
+ Math.min(12, Math.trunc(input?.maxDepth ?? DEFAULT_LIMITS.maxDepth))
1606
+ );
1607
+ const maxEntries = Math.max(
1608
+ 1,
1609
+ Math.min(5e4, Math.trunc(input?.maxEntries ?? DEFAULT_LIMITS.maxEntries))
1610
+ );
1611
+ return { maxDepth, maxEntries };
1612
+ }
1613
+ async function canonicalDirectory(input) {
1614
+ const resolved = path.resolve(input);
1615
+ const real = await fs.realpath(resolved);
1616
+ const stat3 = await fs.stat(real);
1617
+ if (!stat3.isDirectory()) throw new Error(`Project root is not a directory: ${input}`);
1618
+ return real;
1619
+ }
1620
+ async function canonicalInside(input, root, label) {
1621
+ const resolved = path.resolve(input);
1622
+ let real;
1623
+ try {
1624
+ real = await fs.realpath(resolved);
1625
+ } catch (error) {
1626
+ if (error.code !== "ENOENT") throw error;
1627
+ const parent = await fs.realpath(path.dirname(resolved));
1628
+ real = path.join(parent, path.basename(resolved));
1629
+ }
1630
+ if (!isInside(real, root)) throw new Error(`${label} is outside project root: ${input}`);
1631
+ return real;
1632
+ }
1633
+ function resolveFrom(cwd, input) {
1634
+ return path.isAbsolute(input) ? input : path.resolve(cwd, input);
1635
+ }
1636
+ function isInside(candidate, root) {
1637
+ const relative4 = path.relative(root, candidate);
1638
+ return relative4 === "" || !relative4.startsWith("..") && !path.isAbsolute(relative4);
1639
+ }
1640
+ function pathDepth(candidate, root) {
1641
+ const relative4 = path.relative(root, candidate);
1642
+ return relative4 === "" ? 0 : relative4.split(path.sep).length;
1643
+ }
1644
+ function dedupeEvidence(items) {
1645
+ const seen = /* @__PURE__ */ new Set();
1646
+ return items.filter((item) => {
1647
+ const key = `${item.kind}\0${item.path}\0${item.value}\0${item.weight}`;
1648
+ if (seen.has(key)) return false;
1649
+ seen.add(key);
1650
+ return true;
1651
+ });
1652
+ }
1653
+ function compareEvidence(a, b) {
1654
+ return a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind) || b.weight - a.weight;
1655
+ }
1656
+ function compareWorkspaces(a, b) {
1657
+ return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root) || a.id.localeCompare(b.id);
1658
+ }
1659
+
1660
+ // src/languages/diagnostics.ts
1661
+ import * as path2 from "node:path";
1662
+ var MAX_DIAGNOSTICS = 200;
1663
+ function parseLanguageDiagnostics(parser, stdout, stderr, workspaceRoot) {
1664
+ const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
1665
+ let diagnostics;
1666
+ switch (parser) {
1667
+ case "typescript":
1668
+ diagnostics = parseTypeScript(text, workspaceRoot);
1669
+ break;
1670
+ case "cargo-json":
1671
+ diagnostics = parseCargoJson(text, workspaceRoot);
1672
+ break;
1673
+ case "php-lint":
1674
+ diagnostics = parsePhpLint(text, workspaceRoot);
1675
+ break;
1676
+ case "dotnet-build":
1677
+ case "dotnet-format":
1678
+ case "dotnet-test":
1679
+ diagnostics = parseDotnet(text, workspaceRoot);
1680
+ break;
1681
+ case "go-test":
1682
+ case "go-compiler":
1683
+ case "gofmt":
1684
+ diagnostics = parseGo(text, workspaceRoot);
1685
+ break;
1686
+ case "biome":
1687
+ diagnostics = parseBiome(text, workspaceRoot);
1688
+ break;
1689
+ default:
1690
+ diagnostics = parseGeneric(text, parser, workspaceRoot);
1691
+ break;
1692
+ }
1693
+ const sorted = dedupeDiagnostics(diagnostics).sort(compareDiagnostics);
1694
+ const omitted = Math.max(0, sorted.length - MAX_DIAGNOSTICS);
1695
+ const kept = Object.freeze(sorted.slice(0, MAX_DIAGNOSTICS).map((item) => Object.freeze(item)));
1696
+ return {
1697
+ diagnostics: kept,
1698
+ omitted,
1699
+ summary: summarize(kept)
1700
+ };
1701
+ }
1702
+ function diagnosticsForInternalSyntax(language, target, sourceText) {
1703
+ if (language !== "typescript" && language !== "javascript") {
1704
+ return Promise.resolve({ diagnostics: [], omitted: 0, summary: emptySummary() });
1705
+ }
1706
+ return import("@typescript/typescript6").then((tsModule) => {
1707
+ const ts = tsModule.default ?? tsModule;
1708
+ const extension = path2.extname(target).toLowerCase();
1709
+ const scriptKind = extension === ".tsx" ? ts.ScriptKind.TSX : extension === ".ts" || extension === ".mts" || extension === ".cts" ? ts.ScriptKind.TS : ts.ScriptKind.JSX;
1710
+ const sourceFile = ts.createSourceFile(
1711
+ path2.basename(target),
1712
+ sourceText,
1713
+ ts.ScriptTarget.Latest,
1714
+ false,
1715
+ scriptKind
1716
+ );
1717
+ const native = sourceFile.parseDiagnostics ?? [];
1718
+ const diagnostics = native.map((diagnostic) => {
1719
+ const start = diagnostic.start ?? 0;
1720
+ const location = sourceFile.getLineAndCharacterOfPosition(start);
1721
+ return {
1722
+ severity: diagnostic.category === ts.DiagnosticCategory.Warning ? "warning" : "error",
1723
+ ...diagnostic.code ? { code: `TS${diagnostic.code}` } : {},
1724
+ message: ts.flattenDiagnosticMessageText(diagnostic.messageText, " "),
1725
+ file: target,
1726
+ range: { start: { line: location.line + 1, column: location.character + 1 } },
1727
+ source: "typescript-parser"
1728
+ };
1729
+ });
1730
+ const sorted = dedupeDiagnostics(diagnostics).sort(compareDiagnostics);
1731
+ const omitted = Math.max(0, sorted.length - MAX_DIAGNOSTICS);
1732
+ const kept = Object.freeze(sorted.slice(0, MAX_DIAGNOSTICS).map((item) => Object.freeze(item)));
1733
+ return { diagnostics: kept, omitted, summary: summarize(kept) };
1734
+ });
1735
+ }
1736
+ function parseTypeScript(text, root) {
1737
+ const diagnostics = [];
1738
+ const regex = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s*(.+)$/gm;
1739
+ for (const match of text.matchAll(regex)) {
1740
+ diagnostics.push({
1741
+ severity: match[4] === "warning" ? "warning" : "error",
1742
+ code: match[5],
1743
+ message: match[6].trim(),
1744
+ file: normalizeDiagnosticPath(match[1], root),
1745
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
1746
+ source: "typescript"
1747
+ });
1748
+ }
1749
+ return diagnostics;
1750
+ }
1751
+ function parseCargoJson(text, root) {
1752
+ const diagnostics = [];
1753
+ for (const line of text.split(/\r?\n/)) {
1754
+ if (!line.trim().startsWith("{")) continue;
1755
+ try {
1756
+ const value = JSON.parse(line);
1757
+ if (value.reason !== "compiler-message" || !value.message?.message) continue;
1758
+ const primary = value.message.spans?.find((span) => span.is_primary) ?? value.message.spans?.[0];
1759
+ diagnostics.push({
1760
+ severity: normalizeSeverity(value.message.level),
1761
+ ...value.message.code?.code ? { code: value.message.code.code } : {},
1762
+ message: value.message.message,
1763
+ ...primary?.file_name ? { file: normalizeDiagnosticPath(primary.file_name, root) } : {},
1764
+ ...primary?.line_start ? { range: { start: { line: primary.line_start, column: primary.column_start ?? 1 } } } : {},
1765
+ source: "rustc"
1766
+ });
1767
+ } catch {
1768
+ }
1769
+ }
1770
+ return diagnostics;
1771
+ }
1772
+ function parseGo(text, root) {
1773
+ return parseLinePattern(
1774
+ text,
1775
+ /^(.*?\.go):(\d+):(\d+):\s*(.+)$/gm,
1776
+ root,
1777
+ "go",
1778
+ (_match, message) => ({ message, severity: /warning/i.test(message) ? "warning" : "error" })
1779
+ );
1780
+ }
1781
+ function parsePhpLint(text, root) {
1782
+ const diagnostics = [];
1783
+ const regex = /(?:PHP\s+)?(?:Parse|Fatal) error:\s*(.+?)\s+in\s+(.+?)\s+on line\s+(\d+)/gi;
1784
+ for (const match of text.matchAll(regex)) {
1785
+ diagnostics.push({
1786
+ severity: "error",
1787
+ message: match[1].trim(),
1788
+ file: normalizeDiagnosticPath(match[2], root),
1789
+ range: { start: { line: toPositiveInt(match[3]), column: 1 } },
1790
+ source: "php"
1791
+ });
1792
+ }
1793
+ return diagnostics;
1794
+ }
1795
+ function parseDotnet(text, root) {
1796
+ const diagnostics = [];
1797
+ const regex = /^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+([A-Z]+\d+):\s*(.+?)(?:\s+\[.+\])?$/gm;
1798
+ for (const match of text.matchAll(regex)) {
1799
+ diagnostics.push({
1800
+ severity: match[4] === "warning" ? "warning" : "error",
1801
+ code: match[5],
1802
+ message: match[6].trim(),
1803
+ file: normalizeDiagnosticPath(match[1], root),
1804
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
1805
+ source: "dotnet"
1806
+ });
1807
+ }
1808
+ return diagnostics;
1809
+ }
1810
+ function parseBiome(text, root) {
1811
+ const diagnostics = [];
1812
+ const regex = /^(.+?):(\d+):(\d+)\s+(lint\/[^\s]+|format)\s+(.+)$/gm;
1813
+ for (const match of text.matchAll(regex)) {
1814
+ diagnostics.push({
1815
+ severity: "warning",
1816
+ code: match[4],
1817
+ message: match[5].trim(),
1818
+ file: normalizeDiagnosticPath(match[1], root),
1819
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
1820
+ source: "biome"
1821
+ });
1822
+ }
1823
+ return diagnostics;
1824
+ }
1825
+ function parseGeneric(text, source, root) {
1826
+ return parseLinePattern(
1827
+ text,
1828
+ /^(.+?):(\d+):(\d+):\s*(?:(error|warning|info):\s*)?(.+)$/gm,
1829
+ root,
1830
+ source,
1831
+ (match, fallback) => ({
1832
+ severity: normalizeSeverity(match[4]),
1833
+ message: match[5]?.trim() || fallback
1834
+ })
1835
+ );
1836
+ }
1837
+ function parsePackageReports(parser, stdout, stderr) {
1838
+ const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
1839
+ switch (parser) {
1840
+ case "npm-audit":
1841
+ return parseNpmAudit(text);
1842
+ case "npm-outdated":
1843
+ return parseNpmOutdated(text);
1844
+ case "cargo-audit":
1845
+ return parseCargoAudit(text);
1846
+ case "composer-audit":
1847
+ return parseComposerAudit(text);
1848
+ case "composer-outdated":
1849
+ return parseComposerOutdated(text);
1850
+ case "dotnet-package":
1851
+ return parseDotnetPackage(text);
1852
+ default:
1853
+ return { diagnostics: [], vulnerabilities: [], outdated: [] };
1854
+ }
1855
+ }
1856
+ function parseNpmAudit(text) {
1857
+ const advisories = [];
1858
+ const diagnostics = [];
1859
+ let root;
1860
+ try {
1861
+ root = JSON.parse(text);
1862
+ } catch {
1863
+ return { diagnostics, vulnerabilities: advisories, outdated: [] };
1864
+ }
1865
+ const vulnerabilities = root?.vulnerabilities ?? {};
1866
+ const advisoriesRecord = root?.advisories ?? vulnerabilities;
1867
+ for (const [id, value] of Object.entries(advisoriesRecord)) {
1868
+ const advisory = value;
1869
+ const name = advisory.module_name ?? advisory.package_name ?? advisory.name ?? id;
1870
+ advisories.push({
1871
+ package: name,
1872
+ ...advisory.title ? { advisory: advisory.title } : { advisory: id },
1873
+ severity: mapSeverity(advisory.severity),
1874
+ ...advisory.patched_versions ? { fixedIn: advisory.patched_versions } : {},
1875
+ ...advisory.url ? { url: advisory.url } : {}
1876
+ });
1877
+ diagnostics.push({
1878
+ severity: mapSeverity(advisory.severity) === "critical" || mapSeverity(advisory.severity) === "high" ? "error" : "warning",
1879
+ code: id,
1880
+ message: advisory.title ?? `Vulnerability reported for ${name}.`,
1881
+ source: "npm-audit"
1882
+ });
1883
+ }
1884
+ return { diagnostics, vulnerabilities: advisories, outdated: [] };
1885
+ }
1886
+ function parseNpmOutdated(text) {
1887
+ const diagnostics = [];
1888
+ const outdated = [];
1889
+ let payload = {};
1890
+ try {
1891
+ payload = JSON.parse(text);
1892
+ } catch {
1893
+ return { diagnostics, vulnerabilities: [], outdated };
1894
+ }
1895
+ for (const [name, info] of Object.entries(payload)) {
1896
+ const entry = info;
1897
+ if (!entry.latest || entry.latest === entry.current) continue;
1898
+ outdated.push({
1899
+ name,
1900
+ previous: entry.current,
1901
+ resolved: entry.latest,
1902
+ kind: mapOutdatedKind(entry.type)
1903
+ });
1904
+ diagnostics.push({
1905
+ severity: "info",
1906
+ code: "outdated",
1907
+ message: `${name}: ${entry.current ?? "?"} \u2192 ${entry.latest}`,
1908
+ source: "npm-outdated"
1909
+ });
1910
+ }
1911
+ return { diagnostics, vulnerabilities: [], outdated };
1912
+ }
1913
+ function parseCargoAudit(text) {
1914
+ const diagnostics = [];
1915
+ const vulnerabilities = [];
1916
+ let root;
1917
+ try {
1918
+ root = JSON.parse(text);
1919
+ } catch {
1920
+ return { diagnostics, vulnerabilities, outdated: [] };
1921
+ }
1922
+ const findings = root.vulnerabilities?.found;
1923
+ if (!Array.isArray(findings)) return { diagnostics, vulnerabilities, outdated: [] };
1924
+ for (const finding of findings) {
1925
+ const item = finding;
1926
+ const name = item.package ?? "unknown";
1927
+ const advisory = item.id ?? item.advisory?.id ?? "cargo-audit";
1928
+ vulnerabilities.push({
1929
+ package: name,
1930
+ ...item.title ? { advisory: item.title } : { advisory },
1931
+ severity: mapSeverity(item.severity),
1932
+ ...item.patched_versions && item.patched_versions.length > 0 ? { fixedIn: item.patched_versions[0] } : {},
1933
+ ...item.url?.short ? { url: item.url.short } : {}
1934
+ });
1935
+ diagnostics.push({
1936
+ severity: mapSeverity(item.severity) === "critical" || mapSeverity(item.severity) === "high" ? "error" : "warning",
1937
+ code: advisory,
1938
+ message: item.title ?? `${name} reported by cargo-audit.`,
1939
+ source: "cargo-audit"
1940
+ });
1941
+ }
1942
+ return { diagnostics, vulnerabilities, outdated: [] };
1943
+ }
1944
+ function parseComposerAudit(text) {
1945
+ const diagnostics = [];
1946
+ const vulnerabilities = [];
1947
+ const lines = text.split(/\r?\n/);
1948
+ for (const line of lines) {
1949
+ if (!line.trim().startsWith("{")) continue;
1950
+ try {
1951
+ const entry = JSON.parse(line);
1952
+ if (!entry.package) continue;
1953
+ vulnerabilities.push({
1954
+ package: entry.package,
1955
+ ...entry.advisory ? { advisory: entry.advisory } : {},
1956
+ ...entry.title ? { advisory: entry.title } : { advisory: entry.advisory ?? "composer-audit" },
1957
+ severity: mapSeverity(entry.severity),
1958
+ ...entry.affectedVersions ? { fixedIn: entry.affectedVersions } : {},
1959
+ ...entry.url ? { url: entry.url } : {}
1960
+ });
1961
+ diagnostics.push({
1962
+ severity: "warning",
1963
+ code: entry.advisory ?? "composer-audit",
1964
+ message: entry.title ?? `${entry.package} reported by composer audit.`,
1965
+ source: "composer-audit"
1966
+ });
1967
+ } catch {
1968
+ }
1969
+ }
1970
+ return { diagnostics, vulnerabilities, outdated: [] };
1971
+ }
1972
+ function parseComposerOutdated(text) {
1973
+ const diagnostics = [];
1974
+ const outdated = [];
1975
+ const lines = text.split(/\r?\n/);
1976
+ for (const line of lines) {
1977
+ if (!line.trim().startsWith("{")) continue;
1978
+ try {
1979
+ const entry = JSON.parse(line);
1980
+ if (!entry.name || entry.version === entry.latest) continue;
1981
+ outdated.push({
1982
+ name: entry.name,
1983
+ previous: entry.version,
1984
+ resolved: entry.latest ?? entry.version
1985
+ });
1986
+ diagnostics.push({
1987
+ severity: "info",
1988
+ code: "outdated",
1989
+ message: `${entry.name}: ${entry.version ?? "?"} \u2192 ${entry.latest ?? "?"}`,
1990
+ source: "composer-outdated"
1991
+ });
1992
+ } catch {
1993
+ }
1994
+ }
1995
+ return { diagnostics, vulnerabilities: [], outdated };
1996
+ }
1997
+ function parseDotnetPackage(text) {
1998
+ const diagnostics = [];
1999
+ const vulnerabilities = [];
2000
+ const outdated = [];
2001
+ let root;
2002
+ try {
2003
+ root = JSON.parse(text);
2004
+ } catch {
2005
+ return { diagnostics, vulnerabilities, outdated };
2006
+ }
2007
+ const projects = root.projects;
2008
+ if (!Array.isArray(projects)) return { diagnostics, vulnerabilities, outdated };
2009
+ for (const project of projects) {
2010
+ const packages = Array.isArray(project.packages) ? project.packages : [];
2011
+ for (const pkg of packages) {
2012
+ const name = typeof pkg.name === "string" ? pkg.name : "unknown";
2013
+ const requested = typeof pkg.requestedVersion === "string" ? pkg.requestedVersion : void 0;
2014
+ const resolved = typeof pkg.resolvedVersion === "string" ? pkg.resolvedVersion : void 0;
2015
+ const vulnerabilitiesRaw = Array.isArray(pkg.vulnerabilities) ? pkg.vulnerabilities : [];
2016
+ for (const vuln of vulnerabilitiesRaw) {
2017
+ const advisory = typeof vuln.advisoryUrl === "string" ? vuln.advisoryUrl : "dotnet-vulnerable";
2018
+ vulnerabilities.push({
2019
+ package: name,
2020
+ advisory,
2021
+ severity: mapSeverity(typeof vuln.severity === "string" ? vuln.severity : void 0)
2022
+ });
2023
+ }
2024
+ if (vulnerabilitiesRaw.length > 0) {
2025
+ diagnostics.push({
2026
+ severity: "warning",
2027
+ code: "dotnet-vulnerable",
2028
+ message: `${name} has ${vulnerabilitiesRaw.length} known vulnerability entry/entries.`,
2029
+ source: "dotnet-package"
2030
+ });
2031
+ }
2032
+ if (requested && resolved && requested.startsWith(">") && requested.split(">")[1].split(".").slice(0, 2).join(".") !== resolved.split(".").slice(0, 2).join(".")) {
2033
+ outdated.push({ name, requested, resolved });
2034
+ diagnostics.push({
2035
+ severity: "info",
2036
+ code: "outdated",
2037
+ message: `${name}: ${requested} \u2192 ${resolved}`,
2038
+ source: "dotnet-package"
2039
+ });
2040
+ }
2041
+ }
2042
+ }
2043
+ return { diagnostics, vulnerabilities, outdated };
2044
+ }
2045
+ function mapSeverity(value) {
2046
+ switch (value?.toLowerCase()) {
2047
+ case "critical":
2048
+ case "high":
2049
+ return "high";
2050
+ case "medium":
2051
+ case "moderate":
2052
+ return "moderate";
2053
+ case "low":
2054
+ return "low";
2055
+ case "unknown":
2056
+ return "unknown";
2057
+ default:
2058
+ return "unknown";
2059
+ }
2060
+ }
2061
+ function mapOutdatedKind(value) {
2062
+ switch (value?.toLowerCase()) {
2063
+ case "devdependencies":
2064
+ case "development":
2065
+ return "development";
2066
+ case "optionaldependencies":
2067
+ case "optional":
2068
+ return "optional";
2069
+ default:
2070
+ return "runtime";
2071
+ }
2072
+ }
2073
+ function parseLinePattern(text, regex, root, source, details) {
2074
+ const diagnostics = [];
2075
+ for (const match of text.matchAll(regex)) {
2076
+ const parsed = details(match, match.at(-1)?.trim() ?? "Diagnostic");
2077
+ diagnostics.push({
2078
+ severity: parsed.severity,
2079
+ message: parsed.message,
2080
+ file: normalizeDiagnosticPath(match[1], root),
2081
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
2082
+ source
2083
+ });
2084
+ }
2085
+ return diagnostics;
2086
+ }
2087
+ function normalizeDiagnosticPath(value, root) {
2088
+ const clean = value.trim().replace(/^['"]|['"]$/g, "");
2089
+ return path2.resolve(root, clean);
2090
+ }
2091
+ function normalizeSeverity(value) {
2092
+ if (value === "warning" || value === "warn") return "warning";
2093
+ if (value === "info" || value === "note" || value === "help") return "info";
2094
+ if (value === "hint") return "hint";
2095
+ return "error";
2096
+ }
2097
+ function toPositiveInt(value) {
2098
+ return Math.max(1, Number.parseInt(value ?? "1", 10) || 1);
2099
+ }
2100
+ function summarize(diagnostics) {
2101
+ return {
2102
+ errors: diagnostics.filter((item) => item.severity === "error").length,
2103
+ warnings: diagnostics.filter((item) => item.severity === "warning").length,
2104
+ infos: diagnostics.filter((item) => item.severity === "info" || item.severity === "hint").length
2105
+ };
2106
+ }
2107
+ function emptySummary() {
2108
+ return { errors: 0, warnings: 0, infos: 0 };
2109
+ }
2110
+ function dedupeDiagnostics(items) {
2111
+ const seen = /* @__PURE__ */ new Set();
2112
+ return items.filter((item) => {
2113
+ const key = [
2114
+ item.source,
2115
+ item.code ?? "",
2116
+ item.file ?? "",
2117
+ item.range?.start.line ?? 0,
2118
+ item.range?.start.column ?? 0,
2119
+ item.message
2120
+ ].join("\0");
2121
+ if (seen.has(key)) return false;
2122
+ seen.add(key);
2123
+ return true;
2124
+ });
2125
+ }
2126
+ function compareDiagnostics(a, b) {
2127
+ return (a.file ?? "").localeCompare(b.file ?? "") || (a.range?.start.line ?? 0) - (b.range?.start.line ?? 0) || (a.range?.start.column ?? 0) - (b.range?.start.column ?? 0) || severityRank(a.severity) - severityRank(b.severity) || (a.code ?? "").localeCompare(b.code ?? "") || a.message.localeCompare(b.message);
2128
+ }
2129
+ function severityRank(value) {
2130
+ return value === "error" ? 0 : value === "warning" ? 1 : value === "info" ? 2 : 3;
2131
+ }
2132
+
2133
+ // src/languages/execute.ts
2134
+ import * as fs3 from "node:fs/promises";
2135
+ import * as path4 from "node:path";
2136
+
2137
+ // src/languages/plan.ts
2138
+ import * as fs2 from "node:fs/promises";
2139
+ import * as path3 from "node:path";
2140
+ var MAX_ARGUMENTS = 128;
2141
+ var MAX_ARGUMENT_LENGTH = 4096;
2142
+ var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
2143
+ var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
2144
+ var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
2145
+ var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
2146
+ async function planLanguageOperation(options) {
2147
+ validateOperationOptions(options.operation, options.operationOptions?.packages);
2148
+ validateFilterOption(options.operationOptions?.filter);
2149
+ const detection = await detectLanguageWorkspaces(options);
2150
+ const candidates = detection.workspaces.filter(
2151
+ (workspace) => workspace.capabilities.includes(options.operation)
2152
+ );
2153
+ const selected = selectWorkspace(candidates, options);
2154
+ if (selected.status !== "selected") return selected.result;
2155
+ const profile = (options.profiles ?? languageProfileRegistry.list()).find(
2156
+ (item) => item.id === selected.workspace.language
2157
+ );
2158
+ if (!profile) {
2159
+ return {
2160
+ status: "not_found",
2161
+ reason: `Profile ${selected.workspace.language} is unavailable.`,
2162
+ candidates
2163
+ };
2164
+ }
2165
+ if ((profile.id === "typescript" || profile.id === "javascript") && options.operation !== "syntax" && selected.workspace.packageManager === void 0 && new Set(
2166
+ selected.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
2167
+ ).size > 1) {
2168
+ return {
2169
+ status: "unavailable",
2170
+ workspace: selected.workspace,
2171
+ unavailable: {
2172
+ status: "unavailable",
2173
+ profileId: profile.id,
2174
+ workspaceId: selected.workspace.id,
2175
+ operation: options.operation,
2176
+ reason: "Conflicting Node lockfiles make the package manager ambiguous."
2177
+ }
2178
+ };
2179
+ }
2180
+ const resolver = profile.operations[options.operation];
2181
+ if (!resolver) {
2182
+ return {
2183
+ status: "unavailable",
2184
+ workspace: selected.workspace,
2185
+ unavailable: {
2186
+ status: "unavailable",
2187
+ profileId: profile.id,
2188
+ workspaceId: selected.workspace.id,
2189
+ operation: options.operation,
2190
+ reason: `${profile.displayName} does not define ${options.operation}.`
2191
+ }
2192
+ };
2193
+ }
2194
+ const canonicalCwd = options.cwd ? path3.isAbsolute(options.cwd) ? options.cwd : path3.resolve(detection.projectRoot, options.cwd) : detection.projectRoot;
2195
+ const target = options.target ? await canonicalTarget(canonicalCwd, options.target, detection.projectRoot) : void 0;
2196
+ const ctx = {
2197
+ projectRoot: detection.projectRoot,
2198
+ workspace: selected.workspace,
2199
+ ...target ? { target } : {},
2200
+ mode: options.mode ?? "standard",
2201
+ options: options.operationOptions ?? {},
2202
+ platform: process.platform,
2203
+ pathExists: async (candidate) => {
2204
+ try {
2205
+ await fs2.access(path3.resolve(detection.projectRoot, candidate));
2206
+ return true;
2207
+ } catch {
2208
+ return false;
2209
+ }
2210
+ }
2211
+ };
2212
+ const result = await resolver(ctx);
2213
+ if ("status" in result) {
2214
+ return { status: "unavailable", workspace: selected.workspace, unavailable: result };
2215
+ }
2216
+ const errors = validateCommandPlan(result, profile, detection.projectRoot);
2217
+ if (errors.length > 0) {
2218
+ return {
2219
+ status: "unavailable",
2220
+ workspace: selected.workspace,
2221
+ unavailable: {
2222
+ status: "unavailable",
2223
+ profileId: profile.id,
2224
+ workspaceId: selected.workspace.id,
2225
+ operation: options.operation,
2226
+ reason: `Generated plan failed validation: ${errors.join("; ")}`
2227
+ }
2228
+ };
2229
+ }
2230
+ return { status: "planned", workspace: selected.workspace, plan: Object.freeze(result) };
2231
+ }
2232
+ function validateCommandPlan(plan, profile, projectRoot) {
2233
+ const errors = [];
2234
+ if (plan.profileId !== profile.id) errors.push("profile id does not match");
2235
+ if (!isInside2(plan.cwd, projectRoot)) errors.push("cwd is outside project root");
2236
+ if (plan.args.length > MAX_ARGUMENTS) errors.push(`argument count exceeds ${MAX_ARGUMENTS}`);
2237
+ if (plan.args.some((arg) => arg.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(arg))) {
2238
+ errors.push("arguments contain an invalid or oversized value");
2239
+ }
2240
+ if (!Number.isFinite(plan.timeoutMs) || plan.timeoutMs < 1 || plan.timeoutMs > 6e5) {
2241
+ errors.push("timeout is outside 1..600000ms");
2242
+ }
2243
+ if (!Number.isFinite(plan.outputLimitBytes) || plan.outputLimitBytes < 1 || plan.outputLimitBytes > 1e6) {
2244
+ errors.push("output limit is outside 1..1000000 bytes");
2245
+ }
2246
+ if (plan.kind === "internal") {
2247
+ if (plan.command !== null || plan.args.length !== 0)
2248
+ errors.push("internal plans cannot declare a command");
2249
+ } else {
2250
+ if (!plan.command || !profile.executables.includes(plan.command)) {
2251
+ errors.push(`executable "${plan.command ?? ""}" is not allowlisted by the profile`);
2252
+ }
2253
+ }
2254
+ if (Object.keys(plan.env).length > 0)
2255
+ errors.push("Phase 1 plans cannot override environment variables");
2256
+ return errors;
2257
+ }
2258
+ function selectWorkspace(candidates, options) {
2259
+ if (candidates.length === 0) {
2260
+ return {
2261
+ status: "result",
2262
+ result: {
2263
+ status: "not_found",
2264
+ reason: `No workspace supports ${options.operation}.`,
2265
+ candidates: []
2266
+ }
2267
+ };
2268
+ }
2269
+ if (options.workspace) {
2270
+ const requested = path3.isAbsolute(options.workspace) ? path3.resolve(options.workspace) : path3.resolve(options.projectRoot, options.workspace);
2271
+ const matches = candidates.filter(
2272
+ (item) => item.id === options.workspace || path3.resolve(item.root) === requested
2273
+ );
2274
+ if (matches.length === 1) return { status: "selected", workspace: matches[0] };
2275
+ return {
2276
+ status: "result",
2277
+ result: {
2278
+ status: "not_found",
2279
+ reason: `Requested workspace "${options.workspace}" was not detected.`,
2280
+ candidates: [...candidates]
2281
+ }
2282
+ };
2283
+ }
2284
+ if (options.target) {
2285
+ const base = options.cwd ? path3.isAbsolute(options.cwd) ? path3.resolve(options.cwd) : path3.resolve(options.projectRoot, options.cwd) : path3.resolve(options.projectRoot);
2286
+ const target = path3.isAbsolute(options.target) ? path3.resolve(options.target) : path3.resolve(base, options.target);
2287
+ const targeted = candidates.filter((item) => item.evidence.some((evidence) => evidence.kind === "target")).sort(
2288
+ (a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
2289
+ );
2290
+ if (targeted[0]) return { status: "selected", workspace: targeted[0] };
2291
+ const containing = candidates.filter((item) => isInside2(target, item.root)).sort(
2292
+ (a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
2293
+ );
2294
+ if (containing[0]) return { status: "selected", workspace: containing[0] };
2295
+ }
2296
+ const sorted = [...candidates].sort(compareCandidates);
2297
+ const first = sorted[0];
2298
+ const firstDepth = workspaceDepth(first.root, options.projectRoot);
2299
+ const tied = sorted.filter(
2300
+ (item) => item.confidence === first.confidence && workspaceDepth(item.root, options.projectRoot) === firstDepth
2301
+ );
2302
+ if (tied.length > 1) {
2303
+ return {
2304
+ status: "result",
2305
+ result: {
2306
+ status: "ambiguous",
2307
+ reason: "Multiple workspaces have equal confidence; provide target, language, or workspace.",
2308
+ candidates: tied
2309
+ }
2310
+ };
2311
+ }
2312
+ return { status: "selected", workspace: first };
2313
+ }
2314
+ function compareCandidates(a, b) {
2315
+ return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root);
2316
+ }
2317
+ function workspaceDepth(workspaceRoot, projectRoot) {
2318
+ const relative4 = path3.relative(path3.resolve(projectRoot), path3.resolve(workspaceRoot));
2319
+ return relative4 === "" ? 0 : relative4.split(path3.sep).length;
2320
+ }
2321
+ function validateFilterOption(filter) {
2322
+ if (filter === void 0) return;
2323
+ if (filter.startsWith("-") || filter.includes(" --")) {
2324
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
2325
+ }
2326
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
2327
+ throw new Error("Invalid test filter (oversized or contains a control character)");
2328
+ }
2329
+ }
2330
+ function validateOperationOptions(operation, packages) {
2331
+ if (!operation.startsWith("package-") || !packages) return;
2332
+ for (const value of packages) {
2333
+ if (!value || value.length > 214 || value.startsWith("-") || /[\r\n\0;&|`$<>\\]/.test(value)) {
2334
+ throw new Error(`Invalid package identifier "${value}"`);
2335
+ }
2336
+ if (value.startsWith(".") || value.startsWith("/") || /^[A-Za-z]:/.test(value) || value.includes("://")) {
2337
+ throw new Error(`Package paths and URLs are not supported: "${value}"`);
2338
+ }
2339
+ if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value) && !PYTHON_PACKAGE_RE.test(value)) {
2340
+ throw new Error(`Invalid package identifier "${value}"`);
2341
+ }
2342
+ }
2343
+ }
2344
+ async function canonicalTarget(cwd, target, projectRoot) {
2345
+ const resolved = path3.isAbsolute(target) ? path3.resolve(target) : path3.resolve(cwd, target);
2346
+ let real;
2347
+ try {
2348
+ real = await fs2.realpath(resolved);
2349
+ } catch (error) {
2350
+ if (error.code !== "ENOENT") throw error;
2351
+ const parent = await fs2.realpath(path3.dirname(resolved));
2352
+ real = path3.join(parent, path3.basename(resolved));
2353
+ }
2354
+ if (!isInside2(real, projectRoot)) throw new Error(`target is outside project root: ${target}`);
2355
+ return real;
2356
+ }
2357
+ function isInside2(candidate, root) {
2358
+ const relative4 = path3.relative(path3.resolve(root), path3.resolve(candidate));
2359
+ return relative4 === "" || !relative4.startsWith("..") && !path3.isAbsolute(relative4);
2360
+ }
2361
+
2362
+ // src/languages/execute.ts
2363
+ var MAX_INTERNAL_SOURCE_BYTES = 15e5;
2364
+ async function* executeLanguagePlan(options) {
2365
+ const { plan, workspace } = options;
2366
+ if (options.signal.aborted) {
2367
+ return terminalResult(options, "cancelled", options.signal.reason);
2368
+ }
2369
+ const profile = languageProfileRegistry.get(plan.profileId);
2370
+ if (!profile) return unavailableResult(options, `Unknown language profile: ${plan.profileId}`);
2371
+ if (workspace.id !== plan.workspaceId || workspace.language !== plan.profileId) {
2372
+ return unavailableResult(options, "Plan and workspace identity do not match.");
2373
+ }
2374
+ const validation = validateCommandPlan(plan, profile, options.projectRoot);
2375
+ if (validation.length > 0) {
2376
+ return unavailableResult(options, `Plan failed execution validation: ${validation.join("; ")}`);
2377
+ }
2378
+ const containmentError = await validatePlanRealpaths(plan, options.projectRoot);
2379
+ if (containmentError) return unavailableResult(options, containmentError);
2380
+ if (plan.operation.startsWith("package-") || plan.network) {
2381
+ return terminalNonPackageResult(
2382
+ options,
2383
+ "Package and network plans require the separate language_package tool (Phase 3)."
2384
+ );
2385
+ }
2386
+ const startedAt = Date.now();
2387
+ yield {
2388
+ type: "log",
2389
+ text: plan.kind === "internal" ? `Running ${plan.parser}\u2026` : `${plan.command} ${plan.args.join(" ")}`,
2390
+ data: { language: plan.profileId, operation: plan.operation, workspace: workspace.root }
2391
+ };
2392
+ if (plan.kind === "internal") {
2393
+ try {
2394
+ return await executeInternal(options, startedAt);
2395
+ } catch (error) {
2396
+ return unavailableResult(options, error instanceof Error ? error.message : String(error));
2397
+ }
2398
+ }
2399
+ const timeoutController = new AbortController();
2400
+ const timer = setTimeout(
2401
+ () => timeoutController.abort(new Error("language plan timed out")),
2402
+ plan.timeoutMs
2403
+ );
2404
+ timer.unref?.();
2405
+ const signal = AbortSignal.any([options.signal, timeoutController.signal]);
2406
+ let spawned;
2407
+ try {
2408
+ const stream = spawnStream({
2409
+ cmd: plan.command,
2410
+ args: [...plan.args],
2411
+ cwd: plan.cwd,
2412
+ signal,
2413
+ maxBytes: plan.outputLimitBytes
2414
+ });
2415
+ for (; ; ) {
2416
+ const next = await stream.next();
2417
+ if (next.done) {
2418
+ spawned = next.value;
2419
+ break;
2420
+ }
2421
+ yield next.value;
2422
+ }
2423
+ } catch (error) {
2424
+ const timedOut2 = timeoutController.signal.aborted && !options.signal.aborted;
2425
+ const cancelled2 = options.signal.aborted;
2426
+ return {
2427
+ status: timedOut2 ? "timed_out" : cancelled2 ? "cancelled" : "failed",
2428
+ language: plan.profileId,
2429
+ workspace,
2430
+ plan,
2431
+ exitCode: null,
2432
+ durationMs: Date.now() - startedAt,
2433
+ diagnostics: Object.freeze([]),
2434
+ omittedDiagnostics: 0,
2435
+ summary: emptySummary2(),
2436
+ output: "",
2437
+ truncated: false,
2438
+ error: error instanceof Error ? error.message : String(error)
2439
+ };
2440
+ } finally {
2441
+ clearTimeout(timer);
2442
+ }
2443
+ const parsed = parseLanguageDiagnostics(
2444
+ plan.parser,
2445
+ spawned.stdout,
2446
+ spawned.stderr,
2447
+ workspace.root
2448
+ );
2449
+ const timedOut = timeoutController.signal.aborted && !options.signal.aborted;
2450
+ const cancelled = options.signal.aborted;
2451
+ const spawnUnavailable = Boolean(
2452
+ spawned.error && /ENOENT|not found|cannot find/i.test(spawned.error)
2453
+ );
2454
+ const status = timedOut ? "timed_out" : cancelled ? "cancelled" : spawnUnavailable ? "unavailable" : spawned.exitCode === 0 ? "passed" : "failed";
2455
+ const raw = [spawned.stdout, spawned.stderr, spawned.error].filter(Boolean).join("\n");
2456
+ return {
2457
+ status,
2458
+ language: plan.profileId,
2459
+ workspace,
2460
+ plan,
2461
+ exitCode: spawned.exitCode,
2462
+ durationMs: Date.now() - startedAt,
2463
+ diagnostics: parsed.diagnostics,
2464
+ omittedDiagnostics: parsed.omitted,
2465
+ summary: parsed.summary,
2466
+ output: normalizeCommandOutput(raw, { maxBytes: plan.outputLimitBytes }),
2467
+ truncated: spawned.truncated,
2468
+ ...spawned.spoolPath ? { spoolPath: spawned.spoolPath } : {},
2469
+ ...spawned.error ? { error: spawned.error } : {}
2470
+ };
2471
+ }
2472
+ async function executeInternal(options, startedAt) {
2473
+ const target = options.plan.evidence.find((item) => item.kind === "target")?.path;
2474
+ if (!target) return unavailableResult(options, "Internal syntax plan has no target evidence.");
2475
+ const safeTarget = await assertContainedFile(target, options.projectRoot);
2476
+ const stat3 = await fs3.stat(safeTarget);
2477
+ if (stat3.size > MAX_INTERNAL_SOURCE_BYTES) {
2478
+ return unavailableResult(
2479
+ options,
2480
+ `Internal syntax target exceeds ${MAX_INTERNAL_SOURCE_BYTES} bytes.`
2481
+ );
2482
+ }
2483
+ const source = await fs3.readFile(safeTarget, "utf8");
2484
+ const parsed = await diagnosticsForInternalSyntax(options.plan.profileId, safeTarget, source);
2485
+ return {
2486
+ status: parsed.summary.errors > 0 ? "failed" : "passed",
2487
+ language: options.plan.profileId,
2488
+ workspace: options.workspace,
2489
+ plan: options.plan,
2490
+ exitCode: parsed.summary.errors > 0 ? 1 : 0,
2491
+ durationMs: Date.now() - startedAt,
2492
+ diagnostics: parsed.diagnostics,
2493
+ omittedDiagnostics: parsed.omitted,
2494
+ summary: parsed.summary,
2495
+ output: parsed.summary.errors > 0 ? `${parsed.summary.errors} syntax error(s) found.` : "Syntax check passed.",
2496
+ truncated: false
2497
+ };
2498
+ }
2499
+ async function validatePlanRealpaths(plan, projectRoot) {
2500
+ const realRoot = await fs3.realpath(projectRoot);
2501
+ let realCwd;
2502
+ try {
2503
+ realCwd = await fs3.realpath(plan.cwd);
2504
+ } catch (error) {
2505
+ return `Plan cwd is unavailable: ${error instanceof Error ? error.message : String(error)}`;
2506
+ }
2507
+ if (!isRealInside(realCwd, realRoot)) return "Plan cwd resolves outside project root.";
2508
+ for (const argument of plan.args) {
2509
+ if (!path4.isAbsolute(argument)) continue;
2510
+ try {
2511
+ const realArgument = await fs3.realpath(argument);
2512
+ if (!isRealInside(realArgument, realRoot)) {
2513
+ return `Plan argument resolves outside project root: ${argument}`;
2514
+ }
2515
+ } catch (error) {
2516
+ if (error.code !== "ENOENT") {
2517
+ return `Plan argument could not be resolved safely: ${argument}`;
2518
+ }
2519
+ }
2520
+ }
2521
+ return void 0;
2522
+ }
2523
+ function isRealInside(candidate, root) {
2524
+ const relative4 = path4.relative(root, candidate);
2525
+ return relative4 === "" || !relative4.startsWith("..") && !path4.isAbsolute(relative4);
2526
+ }
2527
+ async function assertContainedFile(candidate, projectRoot) {
2528
+ const realRoot = await fs3.realpath(projectRoot);
2529
+ const realTarget = await fs3.realpath(candidate);
2530
+ const relative4 = path4.relative(realRoot, realTarget);
2531
+ if (relative4.startsWith("..") || path4.isAbsolute(relative4)) {
2532
+ throw new Error(`Internal syntax target resolves outside project root: ${candidate}`);
2533
+ }
2534
+ return realTarget;
2535
+ }
2536
+ function terminalResult(options, status, reason) {
2537
+ const message = reason instanceof Error ? reason.message : reason ? String(reason) : status;
2538
+ return {
2539
+ status,
2540
+ language: options.plan.profileId,
2541
+ workspace: options.workspace,
2542
+ plan: options.plan,
2543
+ exitCode: null,
2544
+ durationMs: 0,
2545
+ diagnostics: Object.freeze([]),
2546
+ omittedDiagnostics: 0,
2547
+ summary: emptySummary2(),
2548
+ output: "",
2549
+ truncated: false,
2550
+ error: message
2551
+ };
2552
+ }
2553
+ function unavailableResult(options, reason) {
2554
+ return {
2555
+ status: "unavailable",
2556
+ language: options.plan.profileId,
2557
+ workspace: options.workspace,
2558
+ plan: options.plan,
2559
+ exitCode: null,
2560
+ durationMs: 0,
2561
+ diagnostics: Object.freeze([]),
2562
+ omittedDiagnostics: 0,
2563
+ summary: emptySummary2(),
2564
+ output: reason,
2565
+ truncated: false,
2566
+ error: reason
2567
+ };
2568
+ }
2569
+ function emptySummary2() {
2570
+ return { errors: 0, warnings: 0, infos: 0 };
2571
+ }
2572
+ function terminalNonPackageResult(options, reason) {
2573
+ return {
2574
+ status: "unavailable",
2575
+ language: options.plan.profileId,
2576
+ workspace: options.workspace,
2577
+ plan: options.plan,
2578
+ exitCode: null,
2579
+ durationMs: 0,
2580
+ diagnostics: Object.freeze([]),
2581
+ omittedDiagnostics: 0,
2582
+ summary: emptySummary2(),
2583
+ output: reason,
2584
+ truncated: false,
2585
+ error: reason
2586
+ };
2587
+ }
2588
+ async function* executePackagePlan(options) {
2589
+ const { plan, workspace } = options;
2590
+ const startedAt = Date.now();
2591
+ if (options.signal.aborted) {
2592
+ yield { type: "log", text: "Run cancelled before execution." };
2593
+ return terminalPackageResult(options, "cancelled", startedAt);
2594
+ }
2595
+ const profile = languageProfileRegistry.get(plan.profileId);
2596
+ if (!profile) {
2597
+ yield { type: "warning", text: `Unknown language profile: ${plan.profileId}` };
2598
+ return terminalPackageResult(options, "unavailable", startedAt, "Unknown language profile.");
2599
+ }
2600
+ if (workspace.id !== plan.workspaceId || workspace.language !== plan.profileId) {
2601
+ return terminalPackageResult(
2602
+ options,
2603
+ "unavailable",
2604
+ startedAt,
2605
+ "Plan and workspace identity do not match."
2606
+ );
2607
+ }
2608
+ const validation = validateCommandPlan(plan, profile, options.projectRoot);
2609
+ if (validation.length > 0) {
2610
+ return terminalPackageResult(
2611
+ options,
2612
+ "unavailable",
2613
+ startedAt,
2614
+ `Plan failed execution validation: ${validation.join("; ")}`
2615
+ );
2616
+ }
2617
+ const containmentError = await validatePlanRealpaths(plan, options.projectRoot);
2618
+ if (containmentError) {
2619
+ return terminalPackageResult(options, "unavailable", startedAt, containmentError);
2620
+ }
2621
+ const manifestsBefore = await snapshotPaths(workspace.manifests);
2622
+ const lockfilePaths = collectLockfilePaths(workspace);
2623
+ const lockfilesBefore = await snapshotPaths(lockfilePaths);
2624
+ const manifestSizesBefore = await snapshotSizes(workspace.manifests);
2625
+ const lockfileSizesBefore = await snapshotSizes(lockfilePaths);
2626
+ yield {
2627
+ type: "log",
2628
+ text: plan.kind === "process" ? `${plan.command} ${plan.args.join(" ")}` : `${plan.parser}: ${plan.operation}`,
2629
+ data: {
2630
+ language: plan.profileId,
2631
+ operation: plan.operation,
2632
+ workspace: workspace.root,
2633
+ packages: [...options.packages]
2634
+ }
2635
+ };
2636
+ const timeoutController = new AbortController();
2637
+ const timer = setTimeout(
2638
+ () => timeoutController.abort(new Error("language package plan timed out")),
2639
+ plan.timeoutMs
2640
+ );
2641
+ timer.unref?.();
2642
+ const signal = AbortSignal.any([options.signal, timeoutController.signal]);
2643
+ let spawned;
2644
+ let run;
2645
+ let status = "unavailable";
2646
+ let error;
2647
+ try {
2648
+ if (plan.kind !== "process") {
2649
+ return terminalPackageResult(
2650
+ options,
2651
+ "unavailable",
2652
+ startedAt,
2653
+ "Package operations require an executable plan; internal plans cannot mutate the manifest."
2654
+ );
2655
+ }
2656
+ const stream = spawnStream({
2657
+ cmd: plan.command,
2658
+ args: [...plan.args],
2659
+ cwd: plan.cwd,
2660
+ signal,
2661
+ maxBytes: plan.outputLimitBytes
2662
+ });
2663
+ for (; ; ) {
2664
+ const next = await stream.next();
2665
+ if (next.done) {
2666
+ spawned = next.value;
2667
+ break;
2668
+ }
2669
+ yield next.value;
2670
+ }
2671
+ } catch (error_) {
2672
+ error = error_ instanceof Error ? error_.message : String(error_);
2673
+ } finally {
2674
+ clearTimeout(timer);
2675
+ }
2676
+ if (spawned) {
2677
+ const parsed = parseLanguageDiagnostics(
2678
+ plan.parser,
2679
+ spawned.stdout,
2680
+ spawned.stderr,
2681
+ workspace.root
2682
+ );
2683
+ const timedOut = timeoutController.signal.aborted && !options.signal.aborted;
2684
+ const cancelled = options.signal.aborted;
2685
+ const spawnUnavailable = Boolean(
2686
+ spawned.error && /ENOENT|not found|cannot find/i.test(spawned.error)
2687
+ );
2688
+ status = timedOut ? "timed_out" : cancelled ? "cancelled" : spawnUnavailable ? "unavailable" : spawned.exitCode === 0 ? "passed" : "failed";
2689
+ error ??= spawned.error;
2690
+ const raw = [spawned.stdout, spawned.stderr, spawned.error].filter(Boolean).join("\n");
2691
+ run = {
2692
+ status,
2693
+ language: plan.profileId,
2694
+ workspace,
2695
+ plan,
2696
+ exitCode: spawned.exitCode,
2697
+ durationMs: Date.now() - startedAt,
2698
+ diagnostics: parsed.diagnostics,
2699
+ omittedDiagnostics: parsed.omitted,
2700
+ summary: parsed.summary,
2701
+ output: normalizeCommandOutput(raw, { maxBytes: plan.outputLimitBytes }),
2702
+ truncated: spawned.truncated,
2703
+ ...spawned.spoolPath ? { spoolPath: spawned.spoolPath } : {},
2704
+ ...spawned.error ? { error: spawned.error } : {}
2705
+ };
2706
+ } else {
2707
+ status = options.signal.aborted ? "cancelled" : timeoutController.signal.aborted ? "timed_out" : "failed";
2708
+ }
2709
+ if (status !== "passed") {
2710
+ return terminalPackageResult(
2711
+ options,
2712
+ status,
2713
+ startedAt,
2714
+ error,
2715
+ run,
2716
+ manifestsBefore,
2717
+ lockfilesBefore
2718
+ );
2719
+ }
2720
+ const manifestsAfter = await snapshotPaths(workspace.manifests);
2721
+ const lockfilesAfter = await snapshotPaths(lockfilePaths);
2722
+ const manifestSizesAfter = await snapshotSizes(workspace.manifests);
2723
+ const lockfileSizesAfter = await snapshotSizes(lockfilePaths);
2724
+ const manifestsChanged = await changedPaths(
2725
+ manifestsBefore,
2726
+ manifestsAfter,
2727
+ manifestSizesBefore,
2728
+ manifestSizesAfter
2729
+ );
2730
+ const lockfilesChanged = await changedPaths(
2731
+ lockfilesBefore,
2732
+ lockfilesAfter,
2733
+ lockfileSizesBefore,
2734
+ lockfileSizesAfter
2735
+ );
2736
+ const reports = parsePackageReports(plan.parser, spawned?.stdout ?? "", spawned?.stderr ?? "");
2737
+ const mutations = packageMutationsFromInputs(options.packages, reports.outdated);
2738
+ return {
2739
+ workspace,
2740
+ language: plan.profileId,
2741
+ operation: plan.operation,
2742
+ status,
2743
+ ...run ? { run } : {},
2744
+ durationMs: Date.now() - startedAt,
2745
+ manifestsBefore,
2746
+ lockfilesBefore,
2747
+ manifestsAfter,
2748
+ lockfilesAfter,
2749
+ mutations,
2750
+ vulnerabilities: reports.vulnerabilities,
2751
+ outdated: reports.outdated,
2752
+ manifestsChanged: Object.freeze(manifestsChanged),
2753
+ lockfilesChanged: Object.freeze(lockfilesChanged)
2754
+ };
2755
+ }
2756
+ function packageMutationsFromInputs(inputs, outdated) {
2757
+ const outdatedNames = new Set(outdated.map((entry) => entry.name));
2758
+ return inputs.filter((name) => !outdatedNames.has(name)).map((name) => ({ name, requested: name, kind: "runtime" }));
2759
+ }
2760
+ async function snapshotPaths(paths) {
2761
+ const existing = [];
2762
+ for (const candidate of paths) {
2763
+ try {
2764
+ const stat3 = await fs3.stat(candidate);
2765
+ if (!stat3.isFile()) continue;
2766
+ existing.push(candidate);
2767
+ } catch {
2768
+ }
2769
+ }
2770
+ return Object.freeze(existing);
2771
+ }
2772
+ async function changedPaths(before, after, beforeSizes, afterSizes) {
2773
+ const beforeSet = new Set(before);
2774
+ const afterSet = new Set(after);
2775
+ const changed = /* @__PURE__ */ new Set();
2776
+ for (const path5 of after) {
2777
+ if (!beforeSet.has(path5)) changed.add(path5);
2778
+ }
2779
+ for (const path5 of before) {
2780
+ if (!afterSet.has(path5)) changed.add(path5);
2781
+ }
2782
+ if (beforeSizes && afterSizes) {
2783
+ for (const path5 of after) {
2784
+ if (beforeSizes.get(path5) !== afterSizes.get(path5)) changed.add(path5);
2785
+ }
2786
+ }
2787
+ return [...changed].sort();
2788
+ }
2789
+ async function snapshotSizes(paths) {
2790
+ const sizes = /* @__PURE__ */ new Map();
2791
+ for (const candidate of paths) {
2792
+ try {
2793
+ const stat3 = await fs3.stat(candidate);
2794
+ if (stat3.isFile()) sizes.set(candidate, stat3.size);
2795
+ } catch {
2796
+ }
2797
+ }
2798
+ return sizes;
2799
+ }
2800
+ function collectLockfilePaths(workspace) {
2801
+ const explicit = workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.path);
2802
+ const detected = LOCKFILE_NAMES_BY_PROFILE.get(workspace.language)?.map(
2803
+ (name) => path4.join(workspace.root, name)
2804
+ ) ?? [];
2805
+ const merged = /* @__PURE__ */ new Set();
2806
+ for (const candidate of [...explicit, ...detected]) merged.add(candidate);
2807
+ return Object.freeze([...merged]);
2808
+ }
2809
+ var LOCKFILE_NAMES_BY_PROFILE = /* @__PURE__ */ new Map([
2810
+ ["typescript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
2811
+ ["javascript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
2812
+ ["go", ["go.sum"]],
2813
+ ["rust", ["Cargo.lock"]],
2814
+ ["php", ["composer.lock"]],
2815
+ ["csharp", ["packages.lock.json"]]
2816
+ ]);
2817
+ function terminalPackageResult(options, status, startedAt, error, run, manifestsBefore = [], lockfilesBefore = []) {
2818
+ return {
2819
+ workspace: options.workspace,
2820
+ language: options.plan.profileId,
2821
+ operation: options.plan.operation,
2822
+ status,
2823
+ ...run ? { run } : {},
2824
+ durationMs: Date.now() - startedAt,
2825
+ manifestsBefore,
2826
+ lockfilesBefore,
2827
+ manifestsAfter: manifestsBefore,
2828
+ lockfilesAfter: lockfilesBefore,
2829
+ manifestsChanged: Object.freeze([]),
2830
+ lockfilesChanged: Object.freeze([]),
2831
+ mutations: Object.freeze([]),
2832
+ vulnerabilities: Object.freeze([]),
2833
+ outdated: Object.freeze([]),
2834
+ ...error ? { error } : {}
2835
+ };
2836
+ }
2837
+
2838
+ // src/languages/execute-tool.ts
2839
+ var LANGUAGE_IDS = [
2840
+ "typescript",
2841
+ "javascript",
2842
+ "go",
2843
+ "rust",
2844
+ "php",
2845
+ "csharp",
2846
+ "python",
2847
+ "java",
2848
+ "ruby",
2849
+ "c",
2850
+ "cpp",
2851
+ "swift",
2852
+ "dart",
2853
+ "elixir",
2854
+ "deno",
2855
+ "shell"
2856
+ ];
2857
+ var languageTool = {
2858
+ name: "language",
2859
+ category: "Code Quality",
2860
+ description: "Execute predefined language-specific checks, linters, formatters, tests, builds, and debugging evidence.",
2861
+ usageHint: "Use this instead of constructing compiler or test commands manually. The tool detects the workspace, builds an allowlisted argv plan, revalidates it, executes without a shell, and returns normalized diagnostics. Package and network operations are intentionally excluded.",
2862
+ selection: {
2863
+ doNotUseWhen: "You only need workspace detection/planning, or need to modify dependencies.",
2864
+ useInstead: ["language_info", "install", "audit", "outdated"]
2865
+ },
2866
+ permission: "confirm",
2867
+ // WS-046: gives permission decisions something to key on.
2868
+ // The action performed (build/test/lint); the cwd is where, not what.
2869
+ subjectKey: "action",
2870
+ mutating: true,
2871
+ riskTier: "standard",
2872
+ capabilities: ["shell.restricted", "fs.write"],
2873
+ icon: "code",
2874
+ timeoutMs: 6e5,
2875
+ inputSchema: {
2876
+ type: "object",
2877
+ properties: {
2878
+ action: {
2879
+ type: "string",
2880
+ enum: ["check", "lint", "format", "test", "build", "debug"],
2881
+ description: "Language operation to execute."
2882
+ },
2883
+ cwd: { type: "string", description: "Directory inside the project." },
2884
+ target: { type: "string", description: "Optional target file for workspace selection." },
2885
+ language: { type: "string", enum: LANGUAGE_IDS, description: "Optional language filter." },
2886
+ workspace: { type: "string", description: "Detected workspace id or root." },
2887
+ mode: { type: "string", enum: ["fast", "standard", "thorough"] },
2888
+ check: { type: "string", enum: ["syntax", "semantic", "all"] },
2889
+ formatCheck: {
2890
+ type: "boolean",
2891
+ description: "Verify formatting without writing (default true)."
2892
+ },
2893
+ filter: {
2894
+ type: "string",
2895
+ description: "Validated test/debug filter passed to profile adapters."
2896
+ },
2897
+ coverage: { type: "boolean" },
2898
+ noRun: { type: "boolean", description: "Compile tests without running when supported." },
2899
+ debug: { type: "string", enum: ["compile", "test", "runtime", "race"] }
2900
+ },
2901
+ required: ["action"],
2902
+ additionalProperties: false
2903
+ },
2904
+ validate(input) {
2905
+ const errors = [];
2906
+ if (input.action !== "check" && input.check)
2907
+ errors.push("check is only valid when action=check");
2908
+ if (input.action !== "format" && input.formatCheck !== void 0)
2909
+ errors.push("formatCheck is only valid when action=format");
2910
+ if (input.action !== "debug" && input.debug)
2911
+ errors.push("debug is only valid when action=debug");
2912
+ if (input.coverage) errors.push("coverage collection is not implemented in Phase 2");
2913
+ if (input.noRun && input.action !== "test") errors.push("noRun is only valid when action=test");
2914
+ if (input.filter && (input.filter.length > 500 || input.filter.startsWith("-") || /[\r\n\0;&|`$<>]/.test(input.filter)))
2915
+ errors.push(
2916
+ 'filter must be at most 500 characters, not start with "-", and contain no shell/control characters'
2917
+ );
2918
+ return errors;
2919
+ },
2920
+ async execute(input, ctx, opts) {
2921
+ let final;
2922
+ const stream = languageTool.executeStream;
2923
+ if (!stream) throw new Error("languageTool: stream execution unavailable");
2924
+ for await (const event of stream(input, ctx, opts)) {
2925
+ if (event.type === "final") final = event.output;
2926
+ }
2927
+ if (!final) throw new Error("language: stream ended without final event");
2928
+ return final;
2929
+ },
2930
+ async *executeStream(input, ctx, opts) {
2931
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
2932
+ const operations = operationsFor(input);
2933
+ const runs = [];
2934
+ const unavailableReasons = [];
2935
+ for (const operation of operations) {
2936
+ opts.signal.throwIfAborted();
2937
+ const planResult = await planLanguageOperation({
2938
+ projectRoot: ctx.projectRoot,
2939
+ cwd,
2940
+ operation,
2941
+ ...input.target ? { target: input.target } : {},
2942
+ ...input.language ? { language: input.language } : {},
2943
+ ...input.workspace ? { workspace: input.workspace } : {},
2944
+ ...input.mode ? { mode: input.mode } : {},
2945
+ operationOptions: operationOptions(input),
2946
+ signal: opts.signal
2947
+ });
2948
+ if (planResult.status !== "planned") {
2949
+ const reason = planResult.status === "unavailable" ? planResult.unavailable.reason : planResult.reason;
2950
+ if (runs.length === 0) {
2951
+ return yield {
2952
+ type: "final",
2953
+ output: unavailableOutput(reason)
2954
+ };
2955
+ }
2956
+ unavailableReasons.push(`${operation}: ${reason}`);
2957
+ yield { type: "warning", text: `${operation}: ${reason}` };
2958
+ continue;
2959
+ }
2960
+ const runner = executeLanguagePlan({
2961
+ projectRoot: ctx.projectRoot,
2962
+ workspace: planResult.workspace,
2963
+ plan: planResult.plan,
2964
+ signal: opts.signal
2965
+ });
2966
+ for (; ; ) {
2967
+ const next = await runner.next();
2968
+ if (next.done) {
2969
+ runs.push(next.value);
2970
+ if (next.value.plan.kind === "process") {
2971
+ ctx.recordSideEffect?.({
2972
+ toolUseId: `language-${Date.now()}-${runs.length}`,
2973
+ toolName: "language",
2974
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
2975
+ input: {
2976
+ language: next.value.language,
2977
+ operation: next.value.plan.operation,
2978
+ command: next.value.plan.command,
2979
+ args: next.value.plan.args,
2980
+ cwd: next.value.plan.cwd
2981
+ },
2982
+ outcome: `${next.value.status}${next.value.exitCode === null ? "" : ` (exit ${next.value.exitCode})`}`,
2983
+ risk: "shell"
2984
+ });
2985
+ }
2986
+ break;
2987
+ }
2988
+ yield next.value;
2989
+ }
2990
+ const last = runs.at(-1);
2991
+ if (last.status === "cancelled" || last.status === "timed_out") break;
2992
+ if (last.status === "failed" && input.mode !== "thorough") break;
2993
+ }
2994
+ yield { type: "final", output: aggregateRuns(runs, unavailableReasons) };
2995
+ },
2996
+ serialize(output) {
2997
+ const lines = [
2998
+ `status=${output.status} runs=${output.summary.runs} passed=${output.summary.passed} failed=${output.summary.failed}`,
2999
+ `diagnostics: ${output.summary.errors} error(s), ${output.summary.warnings} warning(s)`
3000
+ ];
3001
+ for (const diagnostic of output.diagnostics.slice(0, 20)) {
3002
+ const location = diagnostic.file ? `${diagnostic.file}${diagnostic.range ? `:${diagnostic.range.start.line}:${diagnostic.range.start.column}` : ""}` : diagnostic.source;
3003
+ lines.push(
3004
+ `${diagnostic.severity.toUpperCase()} ${location}${diagnostic.code ? ` ${diagnostic.code}` : ""}: ${diagnostic.message}`
3005
+ );
3006
+ }
3007
+ if (output.diagnostics.length > 20)
3008
+ lines.push(`\u2026 ${output.diagnostics.length - 20} more diagnostic(s)`);
3009
+ if (output.output) lines.push(output.output);
3010
+ return lines.join("\n");
3011
+ }
3012
+ };
3013
+ function operationsFor(input) {
3014
+ switch (input.action) {
3015
+ case "check":
3016
+ return input.check === "syntax" ? ["syntax"] : input.check === "semantic" ? ["semantic"] : ["syntax", "semantic"];
3017
+ case "lint":
3018
+ return ["lint"];
3019
+ case "format":
3020
+ return [input.formatCheck === false ? "format-write" : "format-check"];
3021
+ case "test":
3022
+ return [input.noRun ? "test-compile" : "test"];
3023
+ case "build":
3024
+ return ["build"];
3025
+ case "debug":
3026
+ if (input.debug === "race") return ["debug-race"];
3027
+ if (input.debug === "test") return ["test-compile", "test"];
3028
+ if (input.debug === "runtime") return ["semantic", "build", "test"];
3029
+ return ["semantic"];
3030
+ }
3031
+ }
3032
+ function operationOptions(input) {
3033
+ return {
3034
+ ...input.target ? { target: input.target } : {},
3035
+ ...input.filter ? { filter: input.filter } : {},
3036
+ ...input.coverage !== void 0 ? { coverage: input.coverage } : {},
3037
+ ...input.noRun !== void 0 ? { noRun: input.noRun } : {}
3038
+ };
3039
+ }
3040
+ function unavailableOutput(reason) {
3041
+ return {
3042
+ status: "unavailable",
3043
+ runs: [],
3044
+ diagnostics: Object.freeze([]),
3045
+ summary: { runs: 0, passed: 0, failed: 0, errors: 0, warnings: 0 },
3046
+ output: reason
3047
+ };
3048
+ }
3049
+ function aggregateRuns(runs, unavailableReasons) {
3050
+ if (runs.length === 0)
3051
+ return unavailableOutput(
3052
+ unavailableReasons.join("\n") || "No executable language operations were planned."
3053
+ );
3054
+ const diagnostics = Object.freeze(runs.flatMap((run) => [...run.diagnostics]));
3055
+ const failed = runs.filter((run) => run.status === "failed").length;
3056
+ const terminal = runs.find((run) => run.status === "cancelled" || run.status === "timed_out");
3057
+ const unavailable2 = runs.filter((run) => run.status === "unavailable").length;
3058
+ const status = terminal?.status ?? (failed > 0 ? "failed" : unavailableReasons.length > 0 || unavailable2 === runs.length ? "unavailable" : "passed");
3059
+ return {
3060
+ status,
3061
+ language: runs[0].language,
3062
+ workspace: runs[0].workspace.root,
3063
+ runs,
3064
+ diagnostics,
3065
+ summary: {
3066
+ runs: runs.length,
3067
+ passed: runs.filter((run) => run.status === "passed").length,
3068
+ failed,
3069
+ errors: diagnostics.filter((item) => item.severity === "error").length,
3070
+ warnings: diagnostics.filter((item) => item.severity === "warning").length
3071
+ },
3072
+ output: [
3073
+ ...runs.map(
3074
+ (run) => `${run.plan.operation}: ${run.status}${run.output ? `
3075
+ ${run.output}` : ""}`
3076
+ ),
3077
+ ...unavailableReasons
3078
+ ].join("\n\n")
3079
+ };
3080
+ }
3081
+
3082
+ // src/languages/package-tool.ts
3083
+ var LANGUAGE_IDS2 = [
3084
+ "typescript",
3085
+ "javascript",
3086
+ "go",
3087
+ "rust",
3088
+ "php",
3089
+ "csharp",
3090
+ "python",
3091
+ "java",
3092
+ "ruby",
3093
+ "c",
3094
+ "cpp",
3095
+ "swift",
3096
+ "dart",
3097
+ "elixir",
3098
+ "deno",
3099
+ "shell"
3100
+ ];
3101
+ var SUPPORTED_OPERATIONS = /* @__PURE__ */ new Set([
3102
+ "install",
3103
+ "add",
3104
+ "remove",
3105
+ "update",
3106
+ "audit",
3107
+ "outdated"
3108
+ ]);
3109
+ var OPERATION_TO_PLAN = {
3110
+ install: "package-install",
3111
+ add: "package-add",
3112
+ remove: "package-remove",
3113
+ update: "package-update",
3114
+ audit: "package-audit",
3115
+ outdated: "package-outdated"
3116
+ };
3117
+ var languagePackageTool = {
3118
+ name: "language_package",
3119
+ category: "Package Management",
3120
+ description: "Restore, mutate, audit, or report outdated packages via predefined ecosystem-specific plans.",
3121
+ usageHint: "Use this instead of the legacy `install`/`audit`/`outdated` tools. The tool detects the workspace, builds an allowlisted argv plan with lifecycle scripts disabled, runs it, and records manifest/lockfile changes.",
3122
+ selection: {
3123
+ doNotUseWhen: "You only need to inspect or plan, or need to compile/test/lint without touching dependencies.",
3124
+ useInstead: ["language_info", "language", "install", "audit", "outdated"]
3125
+ },
3126
+ permission: "confirm",
3127
+ // WS-046: gives permission decisions something to key on.
3128
+ // The package operation performed — install/remove are not interchangeable.
3129
+ subjectKey: "operation",
3130
+ mutating: true,
3131
+ riskTier: "destructive",
3132
+ capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],
3133
+ icon: "package",
3134
+ timeoutMs: 6e5,
3135
+ inputSchema: {
3136
+ type: "object",
3137
+ properties: {
3138
+ operation: {
3139
+ type: "string",
3140
+ enum: ["install", "add", "remove", "update", "audit", "outdated"],
3141
+ description: "Package management operation to perform."
3142
+ },
3143
+ cwd: { type: "string", description: "Directory inside the project." },
3144
+ language: {
3145
+ type: "string",
3146
+ enum: LANGUAGE_IDS2,
3147
+ description: "Optional language profile filter."
3148
+ },
3149
+ workspace: { type: "string", description: "Detected workspace id or root." },
3150
+ names: {
3151
+ type: "array",
3152
+ items: { type: "string" },
3153
+ description: "Validated package names. Required for add/remove/update; optional for install."
3154
+ },
3155
+ scope: {
3156
+ type: "string",
3157
+ enum: ["runtime", "development", "optional"],
3158
+ description: "Where to record the dependency (add/update)."
3159
+ },
3160
+ dryRun: {
3161
+ type: "boolean",
3162
+ description: "Preview the install without modifying the workspace."
3163
+ },
3164
+ allowScripts: {
3165
+ type: "boolean",
3166
+ description: "Opt in to running package lifecycle scripts (preinstall/install/postinstall). Default false."
3167
+ }
3168
+ },
3169
+ required: ["operation"],
3170
+ additionalProperties: false
3171
+ },
3172
+ validate(input) {
3173
+ const errors = [];
3174
+ if (!SUPPORTED_OPERATIONS.has(input.operation))
3175
+ errors.push(`Unsupported package operation: ${String(input.operation)}`);
3176
+ if ((input.operation === "add" || input.operation === "remove") && (!input.names || input.names.length === 0)) {
3177
+ errors.push(`operation=${input.operation} requires at least one name in names`);
3178
+ }
3179
+ for (const name of input.names ?? []) {
3180
+ if (typeof name !== "string" || !name) {
3181
+ errors.push("names must contain only non-empty strings");
3182
+ break;
3183
+ }
3184
+ }
3185
+ return errors;
3186
+ },
3187
+ async execute(input, ctx, opts) {
3188
+ let final;
3189
+ const stream = languagePackageTool.executeStream;
3190
+ if (!stream) throw new Error("languagePackageTool: stream execution unavailable");
3191
+ for await (const event of stream(input, ctx, opts)) {
3192
+ if (event.type === "final") final = event.output;
3193
+ }
3194
+ if (!final) throw new Error("language_package: stream ended without final event");
3195
+ return final;
3196
+ },
3197
+ async *executeStream(input, ctx, opts) {
3198
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
3199
+ const planOperation = OPERATION_TO_PLAN[input.operation];
3200
+ const operationOptions2 = {
3201
+ ...input.names ? { packages: input.names } : {},
3202
+ ...input.scope ? { packageScope: input.scope } : {},
3203
+ ...input.allowScripts !== void 0 ? { allowScripts: input.allowScripts } : {}
3204
+ };
3205
+ const planResult = await planLanguageOperation({
3206
+ projectRoot: ctx.projectRoot,
3207
+ cwd,
3208
+ operation: planOperation,
3209
+ ...input.language ? { language: input.language } : {},
3210
+ ...input.workspace ? { workspace: input.workspace } : {},
3211
+ operationOptions: operationOptions2,
3212
+ signal: opts.signal
3213
+ });
3214
+ if (planResult.status !== "planned") {
3215
+ const reason = planResult.status === "unavailable" ? planResult.unavailable.reason : planResult.reason;
3216
+ yield {
3217
+ type: "final",
3218
+ output: {
3219
+ status: "unavailable",
3220
+ mutations: [],
3221
+ vulnerabilities: [],
3222
+ outdated: [],
3223
+ manifestsChanged: [],
3224
+ lockfilesChanged: [],
3225
+ output: reason,
3226
+ error: reason
3227
+ }
3228
+ };
3229
+ return;
3230
+ }
3231
+ if (input.dryRun) {
3232
+ yield {
3233
+ type: "final",
3234
+ output: {
3235
+ status: "passed",
3236
+ language: planResult.workspace.language,
3237
+ workspace: planResult.workspace.root,
3238
+ operation: planResult.plan.operation,
3239
+ mutations: [],
3240
+ vulnerabilities: [],
3241
+ outdated: [],
3242
+ manifestsChanged: [],
3243
+ lockfilesChanged: [],
3244
+ output: `Dry run: ${planResult.plan.command ?? "internal"} ${planResult.plan.args.join(" ")}`.trim()
3245
+ }
3246
+ };
3247
+ return;
3248
+ }
3249
+ const runner = executePackagePlan({
3250
+ projectRoot: ctx.projectRoot,
3251
+ workspace: planResult.workspace,
3252
+ plan: planResult.plan,
3253
+ packages: input.names ?? [],
3254
+ signal: opts.signal
3255
+ });
3256
+ let outcome;
3257
+ for (; ; ) {
3258
+ const next = await runner.next();
3259
+ if (next.done) {
3260
+ outcome = next.value;
3261
+ break;
3262
+ }
3263
+ yield next.value;
3264
+ }
3265
+ if (!outcome) {
3266
+ yield {
3267
+ type: "final",
3268
+ output: {
3269
+ status: "unavailable",
3270
+ mutations: [],
3271
+ vulnerabilities: [],
3272
+ outdated: [],
3273
+ manifestsChanged: [],
3274
+ lockfilesChanged: [],
3275
+ output: "Language package plan produced no outcome."
3276
+ }
3277
+ };
3278
+ return;
3279
+ }
3280
+ const final = aggregateOutcome(outcome);
3281
+ if (outcome.run?.plan.kind === "process") {
3282
+ ctx.recordSideEffect?.({
3283
+ toolUseId: `language_package-${Date.now()}`,
3284
+ toolName: "language_package",
3285
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
3286
+ input: {
3287
+ language: outcome.language,
3288
+ operation: outcome.operation,
3289
+ command: outcome.run.plan.command,
3290
+ args: outcome.run.plan.args,
3291
+ cwd: outcome.run.plan.cwd,
3292
+ dryRun: input.dryRun ?? false
3293
+ },
3294
+ outcome: `${final.status}${outcome.run.exitCode === null ? "" : ` (exit ${outcome.run.exitCode})`}`,
3295
+ risk: "package"
3296
+ });
3297
+ }
3298
+ yield { type: "final", output: final };
3299
+ },
3300
+ serialize(output) {
3301
+ const lines = [
3302
+ `status=${output.status} operation=${output.operation ?? "unknown"} mutations=${output.mutations.length} vulnerabilities=${output.vulnerabilities.length} outdated=${output.outdated.length}`,
3303
+ `manifestsChanged: ${output.manifestsChanged.join(", ") || "\u2205"}`,
3304
+ `lockfilesChanged: ${output.lockfilesChanged.join(", ") || "\u2205"}`
3305
+ ];
3306
+ for (const mutation of output.mutations) {
3307
+ lines.push(`+ ${mutation.name}${mutation.resolved ? `@${mutation.resolved}` : ""}`);
3308
+ }
3309
+ for (const outdated of output.outdated) {
3310
+ lines.push(`= ${outdated.name}: ${outdated.previous ?? "?"} \u2192 ${outdated.resolved ?? "?"}`);
3311
+ }
3312
+ for (const vuln of output.vulnerabilities) {
3313
+ lines.push(`! ${vuln.package} (${vuln.severity}) ${vuln.advisory ?? ""}`);
3314
+ }
3315
+ if (output.output) lines.push(output.output);
3316
+ return lines.join("\n");
3317
+ }
3318
+ };
3319
+ function aggregateOutcome(outcome) {
3320
+ return {
3321
+ status: outcome.status,
3322
+ language: outcome.language,
3323
+ workspace: outcome.workspace.root,
3324
+ operation: outcome.operation,
3325
+ outcome,
3326
+ mutations: Object.freeze([...outcome.mutations]),
3327
+ vulnerabilities: Object.freeze([...outcome.vulnerabilities]),
3328
+ outdated: Object.freeze([...outcome.outdated]),
3329
+ manifestsChanged: [...outcome.manifestsChanged],
3330
+ lockfilesChanged: [...outcome.lockfilesChanged],
3331
+ output: outputSummary(outcome),
3332
+ ...outcome.run?.output ? { error: void 0 } : {}
3333
+ };
3334
+ }
3335
+ function outputSummary(outcome) {
3336
+ const lines = [
3337
+ `${outcome.operation} on ${outcome.workspace.root}: ${outcome.status}`,
3338
+ `manifestsChanged: ${outcome.manifestsChanged.join(", ") || "\u2205"}`,
3339
+ `lockfilesChanged: ${outcome.lockfilesChanged.join(", ") || "\u2205"}`
3340
+ ];
3341
+ for (const mutation of outcome.mutations) {
3342
+ lines.push(`+ ${mutation.name}${mutation.resolved ? `@${mutation.resolved}` : ""}`);
3343
+ }
3344
+ for (const vuln of outcome.vulnerabilities) {
3345
+ lines.push(`! ${vuln.package} (${vuln.severity})${vuln.advisory ? ` ${vuln.advisory}` : ""}`);
3346
+ }
3347
+ for (const outdated of outcome.outdated) {
3348
+ lines.push(`= ${outdated.name}: ${outdated.previous ?? "?"} \u2192 ${outdated.resolved ?? "?"}`);
3349
+ }
3350
+ if (outcome.run?.output) lines.push(outcome.run.output);
3351
+ return lines.join("\n");
3352
+ }
3353
+
3354
+ // src/languages/tool.ts
3355
+ var LANGUAGE_IDS3 = [
3356
+ "typescript",
3357
+ "javascript",
3358
+ "go",
3359
+ "rust",
3360
+ "php",
3361
+ "csharp",
3362
+ "python",
3363
+ "java",
3364
+ "ruby",
3365
+ "c",
3366
+ "cpp",
3367
+ "swift",
3368
+ "dart",
3369
+ "elixir",
3370
+ "deno",
3371
+ "shell"
3372
+ ];
3373
+ var OPERATIONS = [
3374
+ "syntax",
3375
+ "semantic",
3376
+ "lint",
3377
+ "format-check",
3378
+ "format-write",
3379
+ "test-compile",
3380
+ "test",
3381
+ "build",
3382
+ "run",
3383
+ "debug-compile",
3384
+ "debug-test",
3385
+ "debug-runtime",
3386
+ "debug-race",
3387
+ "package-install",
3388
+ "package-add",
3389
+ "package-remove",
3390
+ "package-update",
3391
+ "package-audit",
3392
+ "package-outdated"
3393
+ ];
3394
+ var languageInfoTool = {
3395
+ name: "language_info",
3396
+ category: "Code Quality",
3397
+ description: "Detect language workspaces and preview predefined language-specific command plans without executing them.",
3398
+ usageHint: "Use `detect` to find TypeScript/JavaScript, Go, Rust, PHP, and C# workspaces. Use `plan` to obtain an exact, validated argv plan before choosing an execution tool. This tool never spawns a process or modifies files.",
3399
+ selection: {
3400
+ doNotUseWhen: "You need to execute a compiler, test runner, formatter, or package manager.",
3401
+ useInstead: ["exec", "typecheck", "lint", "format", "test", "install"]
3402
+ },
3403
+ permission: "auto",
3404
+ mutating: false,
3405
+ riskTier: "safe",
3406
+ capabilities: ["fs.read"],
3407
+ icon: "code",
3408
+ timeoutMs: 3e4,
3409
+ inputSchema: {
3410
+ type: "object",
3411
+ properties: {
3412
+ action: {
3413
+ type: "string",
3414
+ enum: ["detect", "plan", "capabilities"],
3415
+ description: "Read-only action to perform."
3416
+ },
3417
+ cwd: { type: "string", description: "Directory inside the project to inspect." },
3418
+ target: { type: "string", description: "Optional target file used for workspace selection." },
3419
+ language: {
3420
+ type: "string",
3421
+ enum: LANGUAGE_IDS3,
3422
+ description: "Optional language profile filter."
3423
+ },
3424
+ workspace: { type: "string", description: "Workspace id or root returned by detect." },
3425
+ operation: {
3426
+ type: "string",
3427
+ enum: OPERATIONS,
3428
+ description: "Operation to preview when action=plan."
3429
+ },
3430
+ mode: {
3431
+ type: "string",
3432
+ enum: ["fast", "standard", "thorough"],
3433
+ description: "Planning mode."
3434
+ },
3435
+ options: {
3436
+ type: "object",
3437
+ properties: {
3438
+ target: { type: "string" },
3439
+ filter: { type: "string" },
3440
+ coverage: { type: "boolean" },
3441
+ noRun: { type: "boolean" },
3442
+ packages: { type: "array", items: { type: "string" } },
3443
+ packageScope: { type: "string", enum: ["runtime", "development", "optional"] },
3444
+ allowScripts: { type: "boolean" }
3445
+ },
3446
+ additionalProperties: false
3447
+ }
3448
+ },
3449
+ required: ["action"],
3450
+ additionalProperties: false
3451
+ },
3452
+ validate(input) {
3453
+ const errors = [];
3454
+ if (input.action === "plan" && !input.operation)
3455
+ errors.push("operation is required when action=plan");
3456
+ if (input.action !== "plan" && input.operation)
3457
+ errors.push("operation is only valid when action=plan");
3458
+ if (input.action !== "plan" && input.options)
3459
+ errors.push("options are only valid when action=plan");
3460
+ return errors;
3461
+ },
3462
+ async execute(input, ctx, opts) {
3463
+ if (input.action === "capabilities") {
3464
+ return {
3465
+ action: "capabilities",
3466
+ profiles: languageProfileRegistry.list().map((profile) => ({
3467
+ id: profile.id,
3468
+ displayName: profile.displayName,
3469
+ extensions: profile.extensions,
3470
+ operations: Object.keys(profile.operations).sort(),
3471
+ packageManagers: profile.packageManagers
3472
+ }))
3473
+ };
3474
+ }
3475
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
3476
+ const common = {
3477
+ projectRoot: ctx.projectRoot,
3478
+ cwd,
3479
+ ...input.target ? { target: input.target } : {},
3480
+ ...input.language ? { language: input.language } : {},
3481
+ signal: opts.signal
3482
+ };
3483
+ if (input.action === "detect") {
3484
+ return { action: "detect", ...await detectLanguageWorkspaces(common) };
3485
+ }
3486
+ const result = await planLanguageOperation({
3487
+ ...common,
3488
+ operation: input.operation,
3489
+ ...input.workspace ? { workspace: input.workspace } : {},
3490
+ ...input.mode ? { mode: input.mode } : {},
3491
+ ...input.options ? { operationOptions: input.options } : {}
3492
+ });
3493
+ return { action: "plan", ...result };
3494
+ }
3495
+ };
3496
+
3497
+ export {
3498
+ ADDITIONAL_LANGUAGE_PROFILES,
3499
+ PRIMARY_LANGUAGE_PROFILES,
3500
+ LanguageProfileRegistry,
3501
+ validateLanguageProfile,
3502
+ languageProfileRegistry,
3503
+ detectLanguageWorkspaces,
3504
+ parseLanguageDiagnostics,
3505
+ diagnosticsForInternalSyntax,
3506
+ parsePackageReports,
3507
+ planLanguageOperation,
3508
+ validateCommandPlan,
3509
+ executeLanguagePlan,
3510
+ executePackagePlan,
3511
+ languageTool,
3512
+ languagePackageTool,
3513
+ languageInfoTool
3514
+ };
3515
+ //# sourceMappingURL=chunk-DPL47QGS.js.map