@wrongstack/tools 0.284.1 → 0.285.0

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 (311) hide show
  1. package/dist/_concurrency.d.ts +16 -0
  2. package/dist/_concurrency.d.ts.map +1 -0
  3. package/dist/_danger-detect.d.ts +54 -0
  4. package/dist/_danger-detect.d.ts.map +1 -0
  5. package/dist/_edit-match.d.ts +86 -0
  6. package/dist/_edit-match.d.ts.map +1 -0
  7. package/dist/_env.d.ts +6 -0
  8. package/dist/_env.d.ts.map +1 -0
  9. package/dist/_fetch-guard.d.ts +31 -0
  10. package/dist/_fetch-guard.d.ts.map +1 -0
  11. package/dist/_output-spool.d.ts +38 -0
  12. package/dist/_output-spool.d.ts.map +1 -0
  13. package/dist/_redact-command.d.ts +6 -0
  14. package/dist/_redact-command.d.ts.map +1 -0
  15. package/dist/_regex.d.ts +39 -0
  16. package/dist/_regex.d.ts.map +1 -0
  17. package/dist/_session-shell.d.ts +73 -0
  18. package/dist/_session-shell.d.ts.map +1 -0
  19. package/dist/_shell-pick.d.ts +158 -0
  20. package/dist/_shell-pick.d.ts.map +1 -0
  21. package/dist/_spawn-stream.d.ts +32 -0
  22. package/dist/_spawn-stream.d.ts.map +1 -0
  23. package/dist/_syntax-check.d.ts +16 -0
  24. package/dist/_syntax-check.d.ts.map +1 -0
  25. package/dist/_util.d.ts +68 -0
  26. package/dist/_util.d.ts.map +1 -0
  27. package/dist/_win32-resolve.d.ts +42 -0
  28. package/dist/_win32-resolve.d.ts.map +1 -0
  29. package/dist/audit.d.ts +4 -5
  30. package/dist/audit.d.ts.map +1 -0
  31. package/dist/audit.js +2743 -28
  32. package/dist/audit.js.map +7 -1
  33. package/dist/bash-kill-guard.d.ts +62 -0
  34. package/dist/bash-kill-guard.d.ts.map +1 -0
  35. package/dist/bash.d.ts +4 -5
  36. package/dist/bash.d.ts.map +1 -0
  37. package/dist/bash.js +59 -30
  38. package/dist/bash.js.map +7 -1
  39. package/dist/batch-tool-use.d.ts +4 -5
  40. package/dist/batch-tool-use.d.ts.map +1 -0
  41. package/dist/batch-tool-use.js +3 -3
  42. package/dist/batch-tool-use.js.map +7 -1
  43. package/dist/builtin.d.ts +7 -9
  44. package/dist/builtin.d.ts.map +1 -0
  45. package/dist/builtin.js +5282 -1019
  46. package/dist/builtin.js.map +7 -1
  47. package/dist/circuit-breaker.d.ts +4 -5
  48. package/dist/circuit-breaker.d.ts.map +1 -0
  49. package/dist/circuit-breaker.js +3 -3
  50. package/dist/circuit-breaker.js.map +7 -1
  51. package/dist/codebase-index/background-indexer.d.ts +116 -0
  52. package/dist/codebase-index/background-indexer.d.ts.map +1 -0
  53. package/dist/codebase-index/bm25.d.ts +40 -0
  54. package/dist/codebase-index/bm25.d.ts.map +1 -0
  55. package/dist/codebase-index/circuit-breaker.d.ts +94 -0
  56. package/dist/codebase-index/circuit-breaker.d.ts.map +1 -0
  57. package/dist/codebase-index/codebase-index-tool.d.ts +17 -0
  58. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -0
  59. package/dist/codebase-index/codebase-search-tool.d.ts +43 -0
  60. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -0
  61. package/dist/codebase-index/codebase-stats-tool.d.ts +23 -0
  62. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -0
  63. package/dist/codebase-index/gitignore.d.ts +22 -0
  64. package/dist/codebase-index/gitignore.d.ts.map +1 -0
  65. package/dist/codebase-index/go-parser.d.ts +16 -0
  66. package/dist/codebase-index/go-parser.d.ts.map +1 -0
  67. package/dist/codebase-index/index-service.d.ts +25 -0
  68. package/dist/codebase-index/index-service.d.ts.map +1 -0
  69. package/dist/codebase-index/index.d.ts +23 -271
  70. package/dist/codebase-index/index.d.ts.map +1 -0
  71. package/dist/codebase-index/index.js +136 -68
  72. package/dist/codebase-index/index.js.map +7 -1
  73. package/dist/codebase-index/indexer.d.ts +28 -0
  74. package/dist/codebase-index/indexer.d.ts.map +1 -0
  75. package/dist/codebase-index/json-parser.d.ts +8 -0
  76. package/dist/codebase-index/json-parser.d.ts.map +1 -0
  77. package/dist/codebase-index/lsp-kind.d.ts +53 -0
  78. package/dist/codebase-index/lsp-kind.d.ts.map +1 -0
  79. package/dist/codebase-index/py-parser.d.ts +16 -0
  80. package/dist/codebase-index/py-parser.d.ts.map +1 -0
  81. package/dist/codebase-index/refs-extractor.d.ts +25 -0
  82. package/dist/codebase-index/refs-extractor.d.ts.map +1 -0
  83. package/dist/codebase-index/rs-parser.d.ts +8 -0
  84. package/dist/codebase-index/rs-parser.d.ts.map +1 -0
  85. package/dist/codebase-index/schema.d.ts +82 -0
  86. package/dist/codebase-index/schema.d.ts.map +1 -0
  87. package/dist/codebase-index/ts-parser.d.ts +30 -0
  88. package/dist/codebase-index/ts-parser.d.ts.map +1 -0
  89. package/dist/codebase-index/worker-protocol.d.ts +77 -0
  90. package/dist/codebase-index/worker-protocol.d.ts.map +1 -0
  91. package/dist/codebase-index/worker.d.ts +16 -2
  92. package/dist/codebase-index/worker.d.ts.map +1 -0
  93. package/dist/codebase-index/worker.js +103 -65
  94. package/dist/codebase-index/worker.js.map +7 -1
  95. package/dist/codebase-index/writer.d.ts +189 -0
  96. package/dist/codebase-index/writer.d.ts.map +1 -0
  97. package/dist/codebase-index/yaml-parser.d.ts +8 -0
  98. package/dist/codebase-index/yaml-parser.d.ts.map +1 -0
  99. package/dist/design.d.ts +38 -0
  100. package/dist/design.d.ts.map +1 -0
  101. package/dist/design.js +270 -0
  102. package/dist/design.js.map +7 -0
  103. package/dist/diff.d.ts +4 -5
  104. package/dist/diff.d.ts.map +1 -0
  105. package/dist/diff.js +14 -12
  106. package/dist/diff.js.map +7 -1
  107. package/dist/document.d.ts +4 -5
  108. package/dist/document.d.ts.map +1 -0
  109. package/dist/document.js +8 -7
  110. package/dist/document.js.map +7 -1
  111. package/dist/edit.d.ts +5 -36
  112. package/dist/edit.d.ts.map +1 -0
  113. package/dist/edit.js +28 -16
  114. package/dist/edit.js.map +7 -1
  115. package/dist/exec.d.ts +67 -2
  116. package/dist/exec.d.ts.map +1 -0
  117. package/dist/exec.js +50 -25
  118. package/dist/exec.js.map +7 -1
  119. package/dist/fetch.d.ts +4 -32
  120. package/dist/fetch.d.ts.map +1 -0
  121. package/dist/fetch.js +89 -77
  122. package/dist/fetch.js.map +7 -1
  123. package/dist/format.d.ts +4 -5
  124. package/dist/format.d.ts.map +1 -0
  125. package/dist/format.js +2415 -28
  126. package/dist/format.js.map +7 -1
  127. package/dist/git.d.ts +4 -5
  128. package/dist/git.d.ts.map +1 -0
  129. package/dist/git.js +10 -9
  130. package/dist/git.js.map +7 -1
  131. package/dist/glob.d.ts +4 -5
  132. package/dist/glob.d.ts.map +1 -0
  133. package/dist/glob.js +15 -12
  134. package/dist/glob.js.map +7 -1
  135. package/dist/grep.d.ts +4 -5
  136. package/dist/grep.d.ts.map +1 -0
  137. package/dist/grep.js +13 -10
  138. package/dist/grep.js.map +7 -1
  139. package/dist/index.d.ts +19 -724
  140. package/dist/index.d.ts.map +1 -0
  141. package/dist/index.js +6088 -1690
  142. package/dist/index.js.map +7 -1
  143. package/dist/install.d.ts +4 -5
  144. package/dist/install.d.ts.map +1 -0
  145. package/dist/install.js +2680 -31
  146. package/dist/install.js.map +7 -1
  147. package/dist/json.d.ts +4 -5
  148. package/dist/json.d.ts.map +1 -0
  149. package/dist/json.js +13 -11
  150. package/dist/json.js.map +7 -1
  151. package/dist/kanban.d.ts +107 -0
  152. package/dist/kanban.d.ts.map +1 -0
  153. package/dist/kanban.js +845 -0
  154. package/dist/kanban.js.map +7 -0
  155. package/dist/languages/detect.d.ts +3 -0
  156. package/dist/languages/detect.d.ts.map +1 -0
  157. package/dist/languages/diagnostics.d.ts +15 -0
  158. package/dist/languages/diagnostics.d.ts.map +1 -0
  159. package/dist/languages/execute-tool.d.ts +34 -0
  160. package/dist/languages/execute-tool.d.ts.map +1 -0
  161. package/dist/languages/execute.d.ts +18 -0
  162. package/dist/languages/execute.d.ts.map +1 -0
  163. package/dist/languages/index.d.ts +12 -0
  164. package/dist/languages/index.d.ts.map +1 -0
  165. package/dist/languages/index.js +4457 -0
  166. package/dist/languages/index.js.map +7 -0
  167. package/dist/languages/legacy-bridge.d.ts +29 -0
  168. package/dist/languages/legacy-bridge.d.ts.map +1 -0
  169. package/dist/languages/package-tool.d.ts +29 -0
  170. package/dist/languages/package-tool.d.ts.map +1 -0
  171. package/dist/languages/plan.d.ts +4 -0
  172. package/dist/languages/plan.d.ts.map +1 -0
  173. package/dist/languages/profile-helpers.d.ts +14 -0
  174. package/dist/languages/profile-helpers.d.ts.map +1 -0
  175. package/dist/languages/profiles/additional.d.ts +3 -0
  176. package/dist/languages/profiles/additional.d.ts.map +1 -0
  177. package/dist/languages/profiles/primary.d.ts +3 -0
  178. package/dist/languages/profiles/primary.d.ts.map +1 -0
  179. package/dist/languages/registry.d.ts +11 -0
  180. package/dist/languages/registry.d.ts.map +1 -0
  181. package/dist/languages/tool.d.ts +32 -0
  182. package/dist/languages/tool.d.ts.map +1 -0
  183. package/dist/languages/types.d.ts +207 -0
  184. package/dist/languages/types.d.ts.map +1 -0
  185. package/dist/lint.d.ts +4 -5
  186. package/dist/lint.d.ts.map +1 -0
  187. package/dist/lint.js +2415 -27
  188. package/dist/lint.js.map +7 -1
  189. package/dist/logs.d.ts +4 -5
  190. package/dist/logs.d.ts.map +1 -0
  191. package/dist/logs.js +16 -10
  192. package/dist/logs.js.map +7 -1
  193. package/dist/memory.d.ts +7 -8
  194. package/dist/memory.d.ts.map +1 -0
  195. package/dist/memory.js +7 -5
  196. package/dist/memory.js.map +7 -1
  197. package/dist/mode.d.ts +4 -5
  198. package/dist/mode.d.ts.map +1 -0
  199. package/dist/mode.js +3 -3
  200. package/dist/mode.js.map +7 -1
  201. package/dist/next-steps.d.ts +5 -6
  202. package/dist/next-steps.d.ts.map +1 -0
  203. package/dist/next-steps.js +5 -4
  204. package/dist/next-steps.js.map +7 -1
  205. package/dist/outdated.d.ts +4 -5
  206. package/dist/outdated.d.ts.map +1 -0
  207. package/dist/outdated.js +4777 -68
  208. package/dist/outdated.js.map +7 -1
  209. package/dist/pack.d.ts +3 -6
  210. package/dist/pack.d.ts.map +1 -0
  211. package/dist/pack.js +5275 -1019
  212. package/dist/pack.js.map +7 -1
  213. package/dist/patch.d.ts +4 -5
  214. package/dist/patch.d.ts.map +1 -0
  215. package/dist/patch.js +18 -16
  216. package/dist/patch.js.map +7 -1
  217. package/dist/plan.d.ts +55 -0
  218. package/dist/plan.d.ts.map +1 -0
  219. package/dist/plan.js +280 -0
  220. package/dist/plan.js.map +7 -0
  221. package/dist/process-guardian.d.ts +100 -0
  222. package/dist/process-guardian.d.ts.map +1 -0
  223. package/dist/process-registry-persistent.d.ts +121 -0
  224. package/dist/process-registry-persistent.d.ts.map +1 -0
  225. package/dist/process-registry.d.ts +13 -17
  226. package/dist/process-registry.d.ts.map +1 -0
  227. package/dist/process-registry.js +15 -9
  228. package/dist/process-registry.js.map +7 -1
  229. package/dist/ps-slash.d.ts +106 -0
  230. package/dist/ps-slash.d.ts.map +1 -0
  231. package/dist/ps-slash.js +1291 -0
  232. package/dist/ps-slash.js.map +7 -0
  233. package/dist/read.d.ts +4 -5
  234. package/dist/read.d.ts.map +1 -0
  235. package/dist/read.js +13 -11
  236. package/dist/read.js.map +7 -1
  237. package/dist/replace.d.ts +4 -5
  238. package/dist/replace.d.ts.map +1 -0
  239. package/dist/replace.js +26 -13
  240. package/dist/replace.js.map +7 -1
  241. package/dist/scaffold.d.ts +4 -5
  242. package/dist/scaffold.d.ts.map +1 -0
  243. package/dist/scaffold.js +16 -14
  244. package/dist/scaffold.js.map +7 -1
  245. package/dist/search.d.ts +5 -6
  246. package/dist/search.d.ts.map +1 -0
  247. package/dist/search.js +24 -25
  248. package/dist/search.js.map +7 -1
  249. package/dist/set-working-dir.d.ts +18 -0
  250. package/dist/set-working-dir.d.ts.map +1 -0
  251. package/dist/shim/node-sqlite.d.ts +3 -0
  252. package/dist/shim/node-sqlite.d.ts.map +1 -0
  253. package/dist/skill.d.ts +45 -0
  254. package/dist/skill.d.ts.map +1 -0
  255. package/dist/skill.js +175 -0
  256. package/dist/skill.js.map +7 -0
  257. package/dist/spawn-background.d.ts +80 -0
  258. package/dist/spawn-background.d.ts.map +1 -0
  259. package/dist/task.d.ts +35 -0
  260. package/dist/task.d.ts.map +1 -0
  261. package/dist/task.js +380 -0
  262. package/dist/task.js.map +7 -0
  263. package/dist/test.d.ts +4 -5
  264. package/dist/test.d.ts.map +1 -0
  265. package/dist/test.js +2419 -27
  266. package/dist/test.js.map +7 -1
  267. package/dist/todo.d.ts +4 -5
  268. package/dist/todo.d.ts.map +1 -0
  269. package/dist/todo.js +5 -5
  270. package/dist/todo.js.map +7 -1
  271. package/dist/tool-diff.d.ts +16 -17
  272. package/dist/tool-diff.d.ts.map +1 -0
  273. package/dist/tool-diff.js +11 -3
  274. package/dist/tool-diff.js.map +7 -1
  275. package/dist/tool-help.d.ts +4 -5
  276. package/dist/tool-help.d.ts.map +1 -0
  277. package/dist/tool-help.js +3 -3
  278. package/dist/tool-help.js.map +7 -1
  279. package/dist/tool-icon-map.d.ts +45 -0
  280. package/dist/tool-icon-map.d.ts.map +1 -0
  281. package/dist/tool-icons.d.ts +5 -6
  282. package/dist/tool-icons.d.ts.map +1 -0
  283. package/dist/tool-icons.js +16 -4
  284. package/dist/tool-icons.js.map +7 -1
  285. package/dist/tool-search.d.ts +4 -5
  286. package/dist/tool-search.d.ts.map +1 -0
  287. package/dist/tool-search.js +3 -3
  288. package/dist/tool-search.js.map +7 -1
  289. package/dist/tool-summary.d.ts +4 -5
  290. package/dist/tool-summary.d.ts.map +1 -0
  291. package/dist/tool-summary.js +5 -3
  292. package/dist/tool-summary.js.map +7 -1
  293. package/dist/tool-use.d.ts +4 -5
  294. package/dist/tool-use.d.ts.map +1 -0
  295. package/dist/tool-use.js +3 -3
  296. package/dist/tool-use.js.map +7 -1
  297. package/dist/tree.d.ts +4 -5
  298. package/dist/tree.d.ts.map +1 -0
  299. package/dist/tree.js +11 -9
  300. package/dist/tree.js.map +7 -1
  301. package/dist/typecheck.d.ts +4 -5
  302. package/dist/typecheck.d.ts.map +1 -0
  303. package/dist/typecheck.js +2416 -28
  304. package/dist/typecheck.js.map +7 -1
  305. package/dist/write.d.ts +4 -5
  306. package/dist/write.d.ts.map +1 -0
  307. package/dist/write.js +21 -16
  308. package/dist/write.js.map +7 -1
  309. package/package.json +34 -6
  310. package/dist/background-indexer-BeDBxfSh.d.ts +0 -371
  311. package/dist/exec-2OKoT6tk.d.ts +0 -121
package/dist/outdated.js CHANGED
@@ -1,89 +1,4740 @@
1
- import { spawn } from 'node:child_process';
2
- import * as Core from '@wrongstack/core';
3
- import { buildChildEnv } from '@wrongstack/core';
4
- import * as path from 'node:path';
5
- import * as fs from 'node:fs';
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res, err) => function __init() {
4
+ if (err) throw err[0];
5
+ try {
6
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
+ } catch (e) {
8
+ throw err = [e], e;
9
+ }
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+
16
+ // src/_util.ts
17
+ import * as fsp from "node:fs/promises";
18
+ import * as path from "node:path";
19
+ import * as Core from "@wrongstack/core";
20
+ async function detectPackageManager(cwd) {
21
+ const { stat: stat5 } = await import("node:fs/promises");
22
+ try {
23
+ await stat5(`${cwd}/pnpm-lock.yaml`);
24
+ return "pnpm";
25
+ } catch {
26
+ }
27
+ try {
28
+ await stat5(`${cwd}/yarn.lock`);
29
+ return "yarn";
30
+ } catch {
31
+ }
32
+ return "npm";
33
+ }
34
+ function resolvePath(input, ctx) {
35
+ return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
36
+ }
37
+ function allowedRoots(ctx) {
38
+ return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
39
+ }
40
+ function isInsideAny(target, roots) {
41
+ return roots.some((root) => {
42
+ const rel = path.relative(root, target);
43
+ return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
44
+ });
45
+ }
46
+ function ensureInsideRoot(absPath, ctx) {
47
+ const target = path.resolve(absPath);
48
+ if (ctx.allowOutsideProjectRoot) return target;
49
+ if (isInsideAny(target, allowedRoots(ctx))) return target;
50
+ throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
51
+ }
52
+ function safeResolve(input, ctx) {
53
+ return ensureInsideRoot(resolvePath(input, ctx), ctx);
54
+ }
55
+ async function assertRealInsideRoot(absPath, ctx) {
56
+ if (ctx.allowOutsideProjectRoot) return;
57
+ const realRoots = await Promise.all(
58
+ allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
59
+ );
60
+ let probe = absPath;
61
+ for (; ; ) {
62
+ let real;
63
+ try {
64
+ real = await fsp.realpath(probe);
65
+ } catch (err) {
66
+ if (err.code === "ENOENT") {
67
+ const parent = path.dirname(probe);
68
+ if (parent === probe) return;
69
+ probe = parent;
70
+ continue;
71
+ }
72
+ throw err;
73
+ }
74
+ if (isInsideAny(real, realRoots)) return;
75
+ throw new Error(
76
+ `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
77
+ );
78
+ }
79
+ }
80
+ async function safeResolveReal(input, ctx) {
81
+ const abs = safeResolve(input, ctx);
82
+ await assertRealInsideRoot(abs, ctx);
83
+ return abs;
84
+ }
85
+ function collapseCarriageReturns(text) {
86
+ const lf = text.replace(/\r\n/g, "\n");
87
+ if (!lf.includes("\r")) return lf;
88
+ return lf.split("\n").map((line) => line.includes("\r") ? line.slice(line.lastIndexOf("\r") + 1) : line).join("\n");
89
+ }
90
+ function collapseConsecutiveDuplicates(text, minRun = REPEAT_RUN_THRESHOLD) {
91
+ const lines = text.split("\n");
92
+ const out = [];
93
+ let i = 0;
94
+ while (i < lines.length) {
95
+ let j = i + 1;
96
+ while (j < lines.length && lines[j] === lines[i]) j++;
97
+ const run = j - i;
98
+ if (run >= minRun) {
99
+ out.push(lines[i], `\u2026 \u27E8repeated ${run}\xD7\u27E9`);
100
+ } else {
101
+ for (let k = i; k < j; k++) out.push(lines[k]);
102
+ }
103
+ i = j;
104
+ }
105
+ return out.join("\n");
106
+ }
107
+ function takeHeadBytes(s, maxBytes) {
108
+ if (maxBytes <= 0) return "";
109
+ if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
110
+ let lo = 0;
111
+ let hi = s.length;
112
+ while (lo < hi) {
113
+ const mid = Math.ceil((lo + hi) / 2);
114
+ if (Buffer.byteLength(s.slice(0, mid), "utf8") <= maxBytes) lo = mid;
115
+ else hi = mid - 1;
116
+ }
117
+ return s.slice(0, lo);
118
+ }
119
+ function takeTailBytes(s, maxBytes) {
120
+ if (maxBytes <= 0) return "";
121
+ if (Buffer.byteLength(s, "utf8") <= maxBytes) return s;
122
+ let lo = 0;
123
+ let hi = s.length;
124
+ while (lo < hi) {
125
+ const mid = Math.ceil((lo + hi) / 2);
126
+ if (Buffer.byteLength(s.slice(s.length - mid), "utf8") <= maxBytes) lo = mid;
127
+ else hi = mid - 1;
128
+ }
129
+ return s.slice(s.length - lo);
130
+ }
131
+ function truncateHeadTail(s, maxBytes) {
132
+ const total = Buffer.byteLength(s, "utf8");
133
+ if (total <= maxBytes) return s;
134
+ const MARKER_RESERVE = 64;
135
+ const avail = Math.max(0, maxBytes - MARKER_RESERVE);
136
+ const headBudget = Math.floor(avail * 0.45);
137
+ const head = takeHeadBytes(s, headBudget);
138
+ const tail = takeTailBytes(s, avail - Buffer.byteLength(head, "utf8"));
139
+ const kept = Buffer.byteLength(head, "utf8") + Buffer.byteLength(tail, "utf8");
140
+ return `${head}
141
+ \u2026[truncated ${total - kept} bytes]\u2026
142
+ ${tail}`;
143
+ }
144
+ function normalizeCommandOutput(raw, opts = {}) {
145
+ if (!raw) return raw;
146
+ let text = Core.stripAnsi(raw);
147
+ text = collapseCarriageReturns(text);
148
+ text = text.replace(/[ \t]+$/gm, "");
149
+ text = collapseConsecutiveDuplicates(text);
150
+ text = text.replace(/\n{3,}/g, "\n\n");
151
+ return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
152
+ }
153
+ var COMMAND_OUTPUT_MAX_BYTES, REPEAT_RUN_THRESHOLD;
154
+ var init_util = __esm({
155
+ "src/_util.ts"() {
156
+ "use strict";
157
+ COMMAND_OUTPUT_MAX_BYTES = 32768;
158
+ REPEAT_RUN_THRESHOLD = 3;
159
+ }
160
+ });
161
+
162
+ // src/_win32-resolve.ts
163
+ import * as fs from "node:fs";
164
+ import * as path2 from "node:path";
165
+ function resolveWin32Command(cmd) {
166
+ if (process.platform !== "win32") return cmd;
167
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
168
+ return cmd;
169
+ }
170
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
171
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
172
+ for (const dir of pathDirs) {
173
+ const base = path2.join(dir, cmd);
174
+ for (const ext of pathext) {
175
+ const full = `${base}${ext}`;
176
+ try {
177
+ fs.accessSync(full, fs.constants.X_OK);
178
+ return full;
179
+ } catch {
180
+ }
181
+ }
182
+ }
183
+ return cmd;
184
+ }
185
+ function assertSafeWin32ShellArgs(args) {
186
+ for (const arg of args) {
187
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
188
+ throw new Error(
189
+ 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
190
+ );
191
+ }
192
+ }
193
+ }
194
+ function buildWin32CmdShimInvocation(command, args = []) {
195
+ assertSafeWin32ShellArgs([command, ...args]);
196
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
197
+ return {
198
+ command: process.env["COMSPEC"] ?? "cmd.exe",
199
+ args: ["/d", "/c", line],
200
+ windowsVerbatimArguments: true
201
+ };
202
+ }
203
+ function quoteWin32CmdArg(arg) {
204
+ return `"${arg}"`;
205
+ }
206
+ var WIN32_SHELL_META;
207
+ var init_win32_resolve = __esm({
208
+ "src/_win32-resolve.ts"() {
209
+ "use strict";
210
+ WIN32_SHELL_META = /[&|<>"\r\n\0]/;
211
+ }
212
+ });
213
+
214
+ // src/languages/profile-helpers.ts
215
+ function processPlan(ctx, operation, command, args, options) {
216
+ return {
217
+ profileId: ctx.workspace.language,
218
+ workspaceId: ctx.workspace.id,
219
+ operation,
220
+ kind: "process",
221
+ command,
222
+ args: Object.freeze([...args]),
223
+ cwd: ctx.workspace.root,
224
+ env: Object.freeze({}),
225
+ timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
226
+ outputLimitBytes: DEFAULT_OUTPUT_LIMIT_BYTES,
227
+ mutating: options.mutating ?? false,
228
+ network: options.network ?? false,
229
+ executesProjectCode: options.executesProjectCode ?? false,
230
+ reason: options.reason,
231
+ evidence: ctx.workspace.evidence,
232
+ parser: options.parser
233
+ };
234
+ }
235
+ function internalPlan(ctx, operation, parser, reason) {
236
+ return {
237
+ profileId: ctx.workspace.language,
238
+ workspaceId: ctx.workspace.id,
239
+ operation,
240
+ kind: "internal",
241
+ command: null,
242
+ args: Object.freeze([]),
243
+ cwd: ctx.workspace.root,
244
+ env: Object.freeze({}),
245
+ timeoutMs: 5e3,
246
+ outputLimitBytes: 32768,
247
+ mutating: false,
248
+ network: false,
249
+ executesProjectCode: false,
250
+ reason,
251
+ evidence: ctx.workspace.evidence,
252
+ parser
253
+ };
254
+ }
255
+ function unavailable(ctx, operation, reason) {
256
+ return {
257
+ status: "unavailable",
258
+ profileId: ctx.workspace.language,
259
+ workspaceId: ctx.workspace.id,
260
+ operation,
261
+ reason
262
+ };
263
+ }
264
+ function packageNames(ctx) {
265
+ return ctx.options.packages ?? [];
266
+ }
267
+ var DEFAULT_TIMEOUT_MS, DEFAULT_OUTPUT_LIMIT_BYTES;
268
+ var init_profile_helpers = __esm({
269
+ "src/languages/profile-helpers.ts"() {
270
+ "use strict";
271
+ DEFAULT_TIMEOUT_MS = 12e4;
272
+ DEFAULT_OUTPUT_LIMIT_BYTES = 2e5;
273
+ }
274
+ });
275
+
276
+ // src/languages/profiles/additional.ts
277
+ function pythonProfile() {
278
+ return {
279
+ id: "python",
280
+ displayName: "Python",
281
+ extensions: Object.freeze([".py", ".pyi"]),
282
+ lspLanguageIds: Object.freeze(["python"]),
283
+ detectors: Object.freeze([
284
+ { kind: "config", filename: "pyproject.toml", weight: 90 },
285
+ { kind: "config", filename: "setup.py", weight: 70 },
286
+ { kind: "config", filename: "setup.cfg", weight: 60 },
287
+ { kind: "manifest", filename: "requirements.txt", weight: 55 },
288
+ { kind: "manifest", filename: "Pipfile", weight: 50 },
289
+ { kind: "lockfile", filename: "poetry.lock", weight: 30 },
290
+ { kind: "lockfile", filename: "Pipfile.lock", weight: 30 },
291
+ { kind: "lockfile", filename: "uv.lock", weight: 30 }
292
+ ]),
293
+ ignoredDirectories: IGNORES,
294
+ packageManagers: Object.freeze(["pip", "poetry", "pipenv", "uv"]),
295
+ executables: Object.freeze(["python", "python3", "ruff", "black", "pytest", "mypy", "pyright"]),
296
+ operations: Object.freeze({
297
+ syntax: async (ctx) => {
298
+ const py = ctx.options.target ? "python3" : "python3";
299
+ return ctx.options.target ? processPlan(ctx, "syntax", py, ["-m", "py_compile", ctx.options.target], {
300
+ parser: "python",
301
+ reason: "Compile the target file to check for syntax errors."
302
+ }) : unavailable(ctx, "syntax", "Python syntax check requires an explicit target file.");
303
+ },
304
+ semantic: async (ctx) => processPlan(ctx, "semantic", "mypy", [".", "--no-error-summary"], {
305
+ parser: "mypy",
306
+ reason: "Run mypy type checking on the workspace.",
307
+ executesProjectCode: true
308
+ }),
309
+ lint: async (ctx) => processPlan(ctx, "lint", "ruff", ["check", "."], {
310
+ parser: "ruff",
311
+ reason: "Run the Ruff linter on the workspace."
312
+ }),
313
+ "format-check": async (ctx) => processPlan(ctx, "format-check", "ruff", ["format", "--check", "."], {
314
+ parser: "ruff",
315
+ reason: "Check Python formatting without writing files."
316
+ }),
317
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "ruff", ["format", "."], {
318
+ parser: "ruff",
319
+ reason: "Format Python source files.",
320
+ mutating: true
321
+ }),
322
+ test: async (ctx) => processPlan(
323
+ ctx,
324
+ "test",
325
+ "pytest",
326
+ [...ctx.options.filter ? ["-k", ctx.options.filter] : [], "."],
327
+ {
328
+ parser: "pytest",
329
+ reason: ctx.options.filter ? "Run filtered Python tests." : "Run all Python tests.",
330
+ mutating: true,
331
+ executesProjectCode: true
332
+ }
333
+ ),
334
+ build: async (ctx) => unavailable(ctx, "build", "Python is interpreted; use semantic or test instead."),
335
+ "debug-compile": async (ctx) => processPlan(ctx, "debug-compile", "mypy", [".", "--no-error-summary"], {
336
+ parser: "mypy",
337
+ reason: "Collect mypy type diagnostics.",
338
+ executesProjectCode: true
339
+ }),
340
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "pip", ["install", "--no-cache-dir"], {
341
+ parser: "package-text",
342
+ reason: "Install dependencies from requirements.",
343
+ mutating: true,
344
+ network: true
345
+ }),
346
+ "package-add": async (ctx) => {
347
+ const names = packageNames(ctx);
348
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one package name is required.") : processPlan(ctx, "package-add", "pip", ["install", ...names], {
349
+ parser: "package-text",
350
+ reason: "Install specified Python packages.",
351
+ mutating: true,
352
+ network: true
353
+ });
354
+ },
355
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "pip", ["audit"], {
356
+ parser: "pip-audit",
357
+ reason: "Audit Python dependencies for vulnerabilities.",
358
+ network: true
359
+ }),
360
+ run: async (ctx) => {
361
+ const entries = ["main.py", "app.py", "__main__.py", "manage.py"];
362
+ const first = (await Promise.all(
363
+ entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
364
+ )).find(Boolean);
365
+ const args = first ? [first] : [];
366
+ return processPlan(ctx, "run", "python3", args, {
367
+ parser: "command-text",
368
+ reason: first ? `Run the Python entry point ${first}.` : "Run the Python project (no common entry point detected \u2014 add the module path manually).",
369
+ executesProjectCode: true,
370
+ mutating: true
371
+ });
372
+ }
373
+ })
374
+ };
375
+ }
376
+ function hasGradleEvidence(ctx) {
377
+ return ctx.workspace.evidence.some(
378
+ (e) => e.kind === "manifest" && (e.value === "build.gradle" || e.value === "build.gradle.kts" || e.value === "settings.gradle") || e.kind === "lockfile" && e.value === "gradle.lockfile"
379
+ );
380
+ }
381
+ async function gradleRunner(ctx) {
382
+ if (await ctx.pathExists("gradlew")) return "gradlew";
383
+ return "gradle";
384
+ }
385
+ function javaProfile() {
386
+ return {
387
+ id: "java",
388
+ displayName: "Java / Kotlin",
389
+ extensions: Object.freeze([".java", ".kt", ".kts", ".scala"]),
390
+ lspLanguageIds: Object.freeze(["java", "kotlin"]),
391
+ detectors: Object.freeze([
392
+ { kind: "manifest", filename: "pom.xml", weight: 90 },
393
+ { kind: "manifest", filename: "build.gradle", weight: 85 },
394
+ { kind: "manifest", filename: "build.gradle.kts", weight: 85 },
395
+ { kind: "manifest", filename: "settings.gradle", weight: 50 },
396
+ { kind: "lockfile", filename: "gradle.lockfile", weight: 30 }
397
+ ]),
398
+ ignoredDirectories: IGNORES,
399
+ packageManagers: Object.freeze(["maven", "gradle"]),
400
+ executables: Object.freeze(["mvn", "gradle", "gradlew", "java", "kotlinc"]),
401
+ operations: Object.freeze({
402
+ semantic: async (ctx) => {
403
+ const isGradle = hasGradleEvidence(ctx);
404
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
405
+ const args = isGradle ? ["compileJava"] : ["compile", "-q"];
406
+ const reason = isGradle ? "Compile the Gradle project." : "Compile the Maven project.";
407
+ return processPlan(ctx, "semantic", runner, args, {
408
+ parser: isGradle ? "gradle" : "maven",
409
+ reason,
410
+ mutating: true,
411
+ executesProjectCode: true
412
+ });
413
+ },
414
+ lint: async (ctx) => unavailable(
415
+ ctx,
416
+ "lint",
417
+ "Java linting requires a configured checkstyle or spotbugs plugin."
418
+ ),
419
+ "format-check": async (ctx) => unavailable(
420
+ ctx,
421
+ "format-check",
422
+ "Java formatting check requires a configured spotless or google-java-format plugin."
423
+ ),
424
+ test: async (ctx) => {
425
+ const isGradle = hasGradleEvidence(ctx);
426
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
427
+ const args = isGradle ? ["test"] : ["test", "-q"];
428
+ const reason = isGradle ? "Run Gradle tests." : "Run Maven tests.";
429
+ return processPlan(ctx, "test", runner, args, {
430
+ parser: isGradle ? "gradle" : "maven",
431
+ reason,
432
+ mutating: true,
433
+ executesProjectCode: true
434
+ });
435
+ },
436
+ build: async (ctx) => {
437
+ const isGradle = hasGradleEvidence(ctx);
438
+ const runner = isGradle ? await gradleRunner(ctx) : "mvn";
439
+ const args = isGradle ? ["build"] : ["package", "-q", "-DskipTests"];
440
+ const reason = isGradle ? "Build the Gradle project." : "Build the Maven project, skipping tests.";
441
+ return processPlan(ctx, "build", runner, args, {
442
+ parser: isGradle ? "gradle" : "maven",
443
+ reason,
444
+ mutating: true,
445
+ executesProjectCode: true
446
+ });
447
+ },
448
+ run: async (ctx) => {
449
+ if (!hasGradleEvidence(ctx))
450
+ return unavailable(
451
+ ctx,
452
+ "run",
453
+ "Maven run requires exec-maven-plugin configuration. Use `mvn exec:java -q` manually."
454
+ );
455
+ const runner = await gradleRunner(ctx);
456
+ return processPlan(ctx, "run", runner, ["run"], {
457
+ parser: "command-text",
458
+ reason: `Run the Gradle project entry point via ${runner}.`,
459
+ executesProjectCode: true,
460
+ mutating: true
461
+ });
462
+ },
463
+ "package-install": async (ctx) => unavailable(
464
+ ctx,
465
+ "package-install",
466
+ "JVM dependency installation happens through build or dependency:get."
467
+ ),
468
+ "package-add": async (ctx) => {
469
+ const names = packageNames(ctx);
470
+ 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]}`], {
471
+ parser: "maven",
472
+ reason: "Resolve and fetch a Maven dependency.",
473
+ mutating: true,
474
+ network: true
475
+ });
476
+ }
477
+ })
478
+ };
479
+ }
480
+ function rubyProfile() {
481
+ return {
482
+ id: "ruby",
483
+ displayName: "Ruby",
484
+ extensions: Object.freeze([".rb"]),
485
+ lspLanguageIds: Object.freeze(["ruby"]),
486
+ detectors: Object.freeze([
487
+ { kind: "manifest", filename: "Gemfile", weight: 90 },
488
+ { kind: "config", filename: ".ruby-version", weight: 40 },
489
+ { kind: "lockfile", filename: "Gemfile.lock", weight: 30 }
490
+ ]),
491
+ ignoredDirectories: IGNORES,
492
+ packageManagers: Object.freeze(["gem", "bundler"]),
493
+ executables: Object.freeze(["ruby", "gem", "bundle", "rubocop", "rspec"]),
494
+ operations: Object.freeze({
495
+ syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "ruby", ["-c", ctx.options.target], {
496
+ parser: "ruby",
497
+ reason: "Check the target Ruby file for syntax errors."
498
+ }) : unavailable(ctx, "syntax", "Ruby syntax check requires an explicit target file."),
499
+ lint: async (ctx) => processPlan(ctx, "lint", "rubocop", ["--format=json"], {
500
+ parser: "rubocop",
501
+ reason: "Run RuboCop linter.",
502
+ executesProjectCode: true
503
+ }),
504
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "rubocop", ["--auto-correct"], {
505
+ parser: "rubocop",
506
+ reason: "Auto-correct RuboCop violations.",
507
+ mutating: true,
508
+ executesProjectCode: true
509
+ }),
510
+ test: async (ctx) => processPlan(ctx, "test", "rspec", [], {
511
+ parser: "rspec",
512
+ reason: "Run the RSpec test suite.",
513
+ mutating: true,
514
+ executesProjectCode: true
515
+ }),
516
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "bundle", ["install"], {
517
+ parser: "package-text",
518
+ reason: "Install Ruby gem dependencies.",
519
+ mutating: true,
520
+ network: true,
521
+ executesProjectCode: true
522
+ }),
523
+ "package-add": async (ctx) => {
524
+ const names = packageNames(ctx);
525
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one gem name is required.") : processPlan(ctx, "package-add", "gem", ["install", ...names], {
526
+ parser: "package-text",
527
+ reason: "Install specified Ruby gems.",
528
+ mutating: true,
529
+ network: true
530
+ });
531
+ },
532
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "bundle", ["audit", "--format=json"], {
533
+ parser: "bundler-audit",
534
+ reason: "Audit Ruby gems for vulnerabilities.",
535
+ network: true
536
+ }),
537
+ run: async (ctx) => {
538
+ const entries = ["main.rb", "app.rb", "server.rb", "config.ru"];
539
+ const first = (await Promise.all(
540
+ entries.map((name) => ctx.pathExists(name).then((ok) => ok ? name : void 0))
541
+ )).find(Boolean);
542
+ const hasGemfile = await ctx.pathExists("Gemfile");
543
+ const cmd = hasGemfile ? "bundle" : "ruby";
544
+ const args = hasGemfile ? ["exec", "ruby", first ?? ""] : [first ?? ""];
545
+ return processPlan(ctx, "run", cmd, args, {
546
+ parser: "command-text",
547
+ reason: first ? `Run the Ruby entry point ${first}.` : "Run the Ruby project (no common entry point detected \u2014 add the file path manually).",
548
+ executesProjectCode: true,
549
+ mutating: true
550
+ });
551
+ }
552
+ })
553
+ };
554
+ }
555
+ function cProfile() {
556
+ return {
557
+ id: "c",
558
+ displayName: "C / C++",
559
+ extensions: Object.freeze([".c", ".h"]),
560
+ lspLanguageIds: Object.freeze(["c"]),
561
+ detectors: Object.freeze([
562
+ { kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
563
+ { kind: "manifest", filename: "Makefile", weight: 60 },
564
+ { kind: "config", suffix: ".cmake", weight: 50 }
565
+ ]),
566
+ ignoredDirectories: IGNORES,
567
+ packageManagers: Object.freeze([]),
568
+ executables: Object.freeze(["cc", "gcc", "clang", "cmake", "make"]),
569
+ operations: Object.freeze({
570
+ semantic: async (ctx) => processPlan(ctx, "semantic", "cmake", ["--build", ".", "--target", "all"], {
571
+ parser: "cmake",
572
+ reason: "Build C project to collect compiler diagnostics.",
573
+ mutating: true,
574
+ executesProjectCode: true
575
+ }),
576
+ test: async (ctx) => unavailable(
577
+ ctx,
578
+ "test",
579
+ "C test execution requires a configured test runner (ctest, etc.)."
580
+ ),
581
+ build: async (ctx) => processPlan(ctx, "build", "cmake", ["--build", "."], {
582
+ parser: "cmake",
583
+ reason: "Build the C project.",
584
+ mutating: true,
585
+ executesProjectCode: true
586
+ })
587
+ })
588
+ };
589
+ }
590
+ function cppProfile() {
591
+ return {
592
+ ...cProfile(),
593
+ id: "cpp",
594
+ displayName: "C++",
595
+ extensions: Object.freeze([".cpp", ".cc", ".cxx", ".hpp", ".hxx"]),
596
+ lspLanguageIds: Object.freeze(["cpp"]),
597
+ detectors: Object.freeze([
598
+ { kind: "manifest", filename: "CMakeLists.txt", weight: 85 },
599
+ { kind: "manifest", filename: "Makefile", weight: 50 }
600
+ ]),
601
+ executables: Object.freeze(["c++", "g++", "clang++", "cmake", "make"])
602
+ };
603
+ }
604
+ function swiftProfile() {
605
+ return {
606
+ id: "swift",
607
+ displayName: "Swift",
608
+ extensions: Object.freeze([".swift"]),
609
+ lspLanguageIds: Object.freeze(["swift"]),
610
+ detectors: Object.freeze([{ kind: "manifest", filename: "Package.swift", weight: 90 }]),
611
+ ignoredDirectories: IGNORES,
612
+ packageManagers: Object.freeze(["swift"]),
613
+ executables: Object.freeze(["swift", "swiftc"]),
614
+ operations: Object.freeze({
615
+ semantic: async (ctx) => processPlan(ctx, "semantic", "swift", ["build"], {
616
+ parser: "swift",
617
+ reason: "Build Swift package to collect compiler diagnostics.",
618
+ mutating: true,
619
+ executesProjectCode: true
620
+ }),
621
+ test: async (ctx) => processPlan(ctx, "test", "swift", ["test"], {
622
+ parser: "swift-test",
623
+ reason: "Run Swift tests.",
624
+ mutating: true,
625
+ executesProjectCode: true
626
+ }),
627
+ build: async (ctx) => processPlan(ctx, "build", "swift", ["build", "-c", "release"], {
628
+ parser: "swift",
629
+ reason: "Build the Swift package in release mode.",
630
+ mutating: true,
631
+ executesProjectCode: true
632
+ }),
633
+ run: async (ctx) => processPlan(ctx, "run", "swift", ["run"], {
634
+ parser: "command-text",
635
+ reason: "Run the Swift package entry point.",
636
+ executesProjectCode: true
637
+ })
638
+ })
639
+ };
640
+ }
641
+ function dartProfile() {
642
+ return {
643
+ id: "dart",
644
+ displayName: "Dart / Flutter",
645
+ extensions: Object.freeze([".dart"]),
646
+ lspLanguageIds: Object.freeze(["dart"]),
647
+ detectors: Object.freeze([
648
+ { kind: "manifest", filename: "pubspec.yaml", weight: 90 },
649
+ { kind: "lockfile", filename: "pubspec.lock", weight: 30 }
650
+ ]),
651
+ ignoredDirectories: IGNORES,
652
+ packageManagers: Object.freeze(["pub"]),
653
+ executables: Object.freeze(["dart", "flutter"]),
654
+ operations: Object.freeze({
655
+ semantic: async (ctx) => processPlan(ctx, "semantic", "dart", ["analyze"], {
656
+ parser: "dart-analyze",
657
+ reason: "Run Dart static analysis."
658
+ }),
659
+ "format-check": async (ctx) => processPlan(
660
+ ctx,
661
+ "format-check",
662
+ "dart",
663
+ ["format", "--output=none", "--set-exit-if-changed", "."],
664
+ {
665
+ parser: "dart-format",
666
+ reason: "Check Dart formatting without writing files."
667
+ }
668
+ ),
669
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "dart", ["format", "."], {
670
+ parser: "dart-format",
671
+ reason: "Format Dart source files.",
672
+ mutating: true
673
+ }),
674
+ test: async (ctx) => processPlan(ctx, "test", "dart", ["test"], {
675
+ parser: "dart-test",
676
+ reason: "Run Dart tests.",
677
+ mutating: true,
678
+ executesProjectCode: true
679
+ }),
680
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "dart", ["pub", "get"], {
681
+ parser: "package-text",
682
+ reason: "Fetch Dart package dependencies.",
683
+ mutating: true,
684
+ network: true
685
+ }),
686
+ "package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "dart", ["pub", "outdated"], {
687
+ parser: "dart-outdated",
688
+ reason: "Check for outdated Dart packages.",
689
+ network: true
690
+ }),
691
+ run: async (ctx) => processPlan(ctx, "run", "dart", ["run"], {
692
+ parser: "command-text",
693
+ reason: "Run the Dart / Flutter project entry point.",
694
+ executesProjectCode: true
695
+ })
696
+ })
697
+ };
698
+ }
699
+ function denoProfile() {
700
+ return {
701
+ id: "deno",
702
+ displayName: "Deno",
703
+ extensions: Object.freeze([".ts", ".tsx", ".js", ".jsx"]),
704
+ lspLanguageIds: Object.freeze(["typescript", "javascript"]),
705
+ detectors: Object.freeze([
706
+ { kind: "config", filename: "deno.json", weight: 90 },
707
+ { kind: "config", filename: "deno.jsonc", weight: 90 },
708
+ { kind: "config", filename: "import_map.json", weight: 60 }
709
+ ]),
710
+ ignoredDirectories: IGNORES,
711
+ packageManagers: Object.freeze([]),
712
+ executables: Object.freeze(["deno"]),
713
+ // .ts/.js extensions are shared with the TypeScript/JavaScript profiles;
714
+ // only a deno.json(c)/import_map.json detector hit may establish a workspace.
715
+ sourceFallback: false,
716
+ operations: Object.freeze({
717
+ test: async (ctx) => processPlan(ctx, "test", "deno", ["test"], {
718
+ parser: "deno-test",
719
+ reason: "Run Deno tests.",
720
+ mutating: true,
721
+ executesProjectCode: true
722
+ }),
723
+ run: async (ctx) => processPlan(ctx, "run", "deno", ["run", "--allow-all", "main.ts"], {
724
+ parser: "command-text",
725
+ reason: "Run the Deno project entry point with full permission.",
726
+ executesProjectCode: true,
727
+ mutating: true
728
+ })
729
+ })
730
+ };
731
+ }
732
+ function elixirProfile() {
733
+ return {
734
+ id: "elixir",
735
+ displayName: "Elixir",
736
+ extensions: Object.freeze([".ex", ".exs"]),
737
+ lspLanguageIds: Object.freeze(["elixir"]),
738
+ detectors: Object.freeze([
739
+ { kind: "manifest", filename: "mix.exs", weight: 90 },
740
+ { kind: "lockfile", filename: "mix.lock", weight: 30 }
741
+ ]),
742
+ ignoredDirectories: IGNORES,
743
+ packageManagers: Object.freeze(["mix"]),
744
+ executables: Object.freeze(["mix", "elixir"]),
745
+ operations: Object.freeze({
746
+ semantic: async (ctx) => unavailable(ctx, "semantic", "Elixir compilation is handled by the build operation."),
747
+ lint: async (ctx) => unavailable(ctx, "lint", "Elixir linting requires the credo package."),
748
+ test: async (ctx) => processPlan(ctx, "test", "mix", ["test"], {
749
+ parser: "mix-test",
750
+ reason: "Run ExUnit tests.",
751
+ mutating: true,
752
+ executesProjectCode: true
753
+ }),
754
+ build: async (ctx) => processPlan(ctx, "build", "mix", ["compile"], {
755
+ parser: "mix",
756
+ reason: "Compile the Mix project.",
757
+ mutating: true,
758
+ executesProjectCode: true
759
+ }),
760
+ run: async (ctx) => processPlan(ctx, "run", "mix", ["run"], {
761
+ parser: "command-text",
762
+ reason: "Run the Mix project entry point.",
763
+ executesProjectCode: true
764
+ }),
765
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "mix", ["deps.get"], {
766
+ parser: "package-text",
767
+ reason: "Fetch Elixir dependencies.",
768
+ mutating: true,
769
+ network: true
770
+ })
771
+ })
772
+ };
773
+ }
774
+ function shellProfile() {
775
+ return {
776
+ id: "shell",
777
+ displayName: "Shell",
778
+ extensions: Object.freeze([".sh", ".bash"]),
779
+ lspLanguageIds: Object.freeze(["shellscript"]),
780
+ detectors: Object.freeze([{ kind: "config", filename: "ShellCheckrc", weight: 50 }]),
781
+ ignoredDirectories: IGNORES,
782
+ packageManagers: Object.freeze([]),
783
+ executables: Object.freeze(["bash", "sh", "shellcheck", "shfmt"]),
784
+ operations: Object.freeze({
785
+ syntax: async (ctx) => ctx.options.target ? processPlan(ctx, "syntax", "bash", ["-n", ctx.options.target], {
786
+ parser: "shell",
787
+ reason: "Check the shell script for syntax errors."
788
+ }) : unavailable(ctx, "syntax", "Shell syntax check requires an explicit target file."),
789
+ lint: async (ctx) => ctx.options.target ? processPlan(ctx, "lint", "shellcheck", ["--format=json", ctx.options.target], {
790
+ parser: "shellcheck",
791
+ reason: "Run ShellCheck on the target script."
792
+ }) : unavailable(ctx, "lint", "Shell linting requires an explicit target file."),
793
+ "format-check": async (ctx) => ctx.options.target ? processPlan(ctx, "format-check", "shfmt", ["-d", ctx.options.target], {
794
+ parser: "shell",
795
+ reason: "Check shell formatting without writing."
796
+ }) : unavailable(
797
+ ctx,
798
+ "format-check",
799
+ "Shell formatting check requires an explicit target file."
800
+ ),
801
+ "format-write": async (ctx) => ctx.options.target ? processPlan(ctx, "format-write", "shfmt", ["-w", ctx.options.target], {
802
+ parser: "shell",
803
+ reason: "Format the shell script.",
804
+ mutating: true
805
+ }) : unavailable(ctx, "format-write", "Shell formatting requires an explicit target file.")
806
+ })
807
+ };
808
+ }
809
+ var IGNORES, ADDITIONAL_LANGUAGE_PROFILES;
810
+ var init_additional = __esm({
811
+ "src/languages/profiles/additional.ts"() {
812
+ "use strict";
813
+ init_profile_helpers();
814
+ IGNORES = Object.freeze([
815
+ ".git",
816
+ ".wrongstack",
817
+ "node_modules",
818
+ "vendor",
819
+ "dist",
820
+ "build",
821
+ "coverage"
822
+ ]);
823
+ ADDITIONAL_LANGUAGE_PROFILES = Object.freeze([
824
+ pythonProfile(),
825
+ javaProfile(),
826
+ rubyProfile(),
827
+ cProfile(),
828
+ cppProfile(),
829
+ swiftProfile(),
830
+ dartProfile(),
831
+ denoProfile(),
832
+ elixirProfile(),
833
+ shellProfile()
834
+ ]);
835
+ }
836
+ });
837
+
838
+ // src/languages/profiles/primary.ts
839
+ function nodeManager(ctx) {
840
+ const lockfiles = new Set(
841
+ ctx.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
842
+ );
843
+ if (lockfiles.size > 1 && !ctx.workspace.packageManager) return void 0;
844
+ return ctx.workspace.packageManager ?? "npm";
845
+ }
846
+ function scriptPlan(ctx, operation, script) {
847
+ const manager = nodeManager(ctx);
848
+ if (!manager)
849
+ return unavailable(
850
+ ctx,
851
+ operation,
852
+ "Conflicting Node lockfiles make the package manager ambiguous."
853
+ );
854
+ const args = manager === "npm" ? ["run", script] : [script];
855
+ return processPlan(ctx, operation, manager, args, {
856
+ parser: "command-text",
857
+ reason: `Run the detected ${manager} ${script} script for this workspace.`,
858
+ mutating: true,
859
+ executesProjectCode: true
860
+ });
861
+ }
862
+ function nodeExec(ctx, executable, args) {
863
+ const manager = ctx.workspace.packageManager ?? "npm";
864
+ if (manager === "pnpm") return { command: "pnpm", args: ["exec", executable, ...args] };
865
+ if (manager === "yarn") return { command: "yarn", args: ["exec", executable, ...args] };
866
+ if (manager === "bun") return { command: "bun", args: ["x", executable, ...args] };
867
+ return { command: "npx", args: ["--no-install", executable, ...args] };
868
+ }
869
+ function typescriptProfile() {
870
+ return {
871
+ id: "typescript",
872
+ displayName: "TypeScript",
873
+ extensions: Object.freeze([".ts", ".tsx", ".mts", ".cts"]),
874
+ lspLanguageIds: Object.freeze(["typescript", "typescriptreact"]),
875
+ detectors: Object.freeze([
876
+ { kind: "config", filename: "tsconfig.json", weight: 90 },
877
+ { kind: "manifest", filename: "package.json", weight: 55 },
878
+ { kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
879
+ { kind: "lockfile", filename: "yarn.lock", weight: 30 },
880
+ { kind: "lockfile", filename: "package-lock.json", weight: 30 },
881
+ { kind: "lockfile", filename: "bun.lock", weight: 30 },
882
+ { kind: "lockfile", filename: "bun.lockb", weight: 30 }
883
+ ]),
884
+ ignoredDirectories: COMMON_IGNORES,
885
+ packageManagers: Object.freeze(["pnpm", "yarn", "bun", "npm"]),
886
+ executables: Object.freeze(["pnpm", "yarn", "bun", "npx", "npm"]),
887
+ operations: Object.freeze({
888
+ syntax: async (ctx) => internalPlan(
889
+ ctx,
890
+ "syntax",
891
+ "typescript-parser",
892
+ "Parse the target with the TypeScript compiler API."
893
+ ),
894
+ semantic: async (ctx) => {
895
+ const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
896
+ return processPlan(ctx, "semantic", run.command, run.args, {
897
+ parser: "typescript",
898
+ reason: "Run the workspace TypeScript compiler without emitting files.",
899
+ executesProjectCode: true
900
+ });
901
+ },
902
+ lint: async (ctx) => {
903
+ const run = nodeExec(ctx, "biome", ["lint", "."]);
904
+ return processPlan(ctx, "lint", run.command, run.args, {
905
+ parser: "biome",
906
+ reason: "Run the project-local Biome linter.",
907
+ executesProjectCode: true
908
+ });
909
+ },
910
+ "format-check": async (ctx) => {
911
+ const run = nodeExec(ctx, "biome", ["format", "--check", "."]);
912
+ return processPlan(ctx, "format-check", run.command, run.args, {
913
+ parser: "biome",
914
+ reason: "Check formatting with the project-local Biome formatter.",
915
+ executesProjectCode: true
916
+ });
917
+ },
918
+ "format-write": async (ctx) => {
919
+ const run = nodeExec(ctx, "biome", ["format", "--write", "."]);
920
+ return processPlan(ctx, "format-write", run.command, run.args, {
921
+ parser: "biome",
922
+ reason: "Format the workspace with the project-local Biome formatter.",
923
+ mutating: true,
924
+ executesProjectCode: true
925
+ });
926
+ },
927
+ test: async (ctx) => ctx.options.filter || ctx.options.coverage ? unavailable(
928
+ ctx,
929
+ "test",
930
+ "The detected package script does not expose deterministic filter or coverage adapters."
931
+ ) : scriptPlan(ctx, "test", "test"),
932
+ build: async (ctx) => scriptPlan(ctx, "build", "build"),
933
+ run: async (ctx) => scriptPlan(ctx, "run", "dev"),
934
+ "debug-compile": async (ctx) => {
935
+ const run = nodeExec(ctx, "tsc", ["--noEmit", "--pretty", "false"]);
936
+ return processPlan(ctx, "debug-compile", run.command, run.args, {
937
+ parser: "typescript",
938
+ reason: "Collect deterministic TypeScript compiler diagnostics.",
939
+ executesProjectCode: true
940
+ });
941
+ },
942
+ "package-install": async (ctx) => {
943
+ const manager = ctx.workspace.packageManager ?? "npm";
944
+ const args = manager === "yarn" ? ["install", "--ignore-scripts"] : ["install", "--ignore-scripts"];
945
+ return processPlan(ctx, "package-install", manager, args, {
946
+ parser: "package-text",
947
+ reason: `Restore declared dependencies with ${manager} and lifecycle scripts disabled.`,
948
+ mutating: true,
949
+ network: true,
950
+ executesProjectCode: false
951
+ });
952
+ },
953
+ "package-add": async (ctx) => {
954
+ const names = packageNames(ctx);
955
+ if (names.length === 0)
956
+ return unavailable(ctx, "package-add", "At least one package name is required.");
957
+ const manager = ctx.workspace.packageManager ?? "npm";
958
+ const args = manager === "npm" ? ["install", "--ignore-scripts", ...names] : ["add", "--ignore-scripts", ...names];
959
+ return processPlan(ctx, "package-add", manager, args, {
960
+ parser: "package-text",
961
+ reason: `Add validated packages with ${manager} and lifecycle scripts disabled.`,
962
+ mutating: true,
963
+ network: true
964
+ });
965
+ },
966
+ "package-audit": async (ctx) => {
967
+ const manager = ctx.workspace.packageManager ?? "npm";
968
+ return processPlan(ctx, "package-audit", manager, ["audit", "--json"], {
969
+ parser: "npm-audit",
970
+ reason: `Audit dependencies with the detected ${manager} package manager.`,
971
+ network: true
972
+ });
973
+ },
974
+ "package-outdated": async (ctx) => {
975
+ const manager = ctx.workspace.packageManager ?? "npm";
976
+ return processPlan(ctx, "package-outdated", manager, ["outdated", "--json"], {
977
+ parser: "npm-outdated",
978
+ reason: `Check outdated dependencies with ${manager}.`,
979
+ network: true
980
+ });
981
+ }
982
+ })
983
+ };
984
+ }
985
+ function javascriptProfile() {
986
+ const ts = typescriptProfile();
987
+ return {
988
+ ...ts,
989
+ id: "javascript",
990
+ displayName: "JavaScript",
991
+ extensions: Object.freeze([".js", ".jsx", ".mjs", ".cjs"]),
992
+ lspLanguageIds: Object.freeze(["javascript", "javascriptreact"]),
993
+ detectors: Object.freeze([
994
+ { kind: "config", filename: "jsconfig.json", weight: 90 },
995
+ { kind: "manifest", filename: "package.json", weight: 70 },
996
+ { kind: "lockfile", filename: "pnpm-lock.yaml", weight: 30 },
997
+ { kind: "lockfile", filename: "yarn.lock", weight: 30 },
998
+ { kind: "lockfile", filename: "package-lock.json", weight: 30 },
999
+ { kind: "lockfile", filename: "bun.lock", weight: 30 },
1000
+ { kind: "lockfile", filename: "bun.lockb", weight: 30 }
1001
+ ]),
1002
+ operations: Object.freeze({
1003
+ ...ts.operations,
1004
+ syntax: async (ctx) => internalPlan(
1005
+ ctx,
1006
+ "syntax",
1007
+ "typescript-parser",
1008
+ "Parse JavaScript with the TypeScript compiler API."
1009
+ )
1010
+ })
1011
+ };
1012
+ }
1013
+ var COMMON_IGNORES, goProfile, rustProfile, phpProfile, csharpProfile, PRIMARY_LANGUAGE_PROFILES;
1014
+ var init_primary = __esm({
1015
+ "src/languages/profiles/primary.ts"() {
1016
+ "use strict";
1017
+ init_profile_helpers();
1018
+ COMMON_IGNORES = Object.freeze([
1019
+ ".git",
1020
+ ".wrongstack",
1021
+ "node_modules",
1022
+ "vendor",
1023
+ "target",
1024
+ "bin",
1025
+ "obj",
1026
+ "dist",
1027
+ "build",
1028
+ "coverage"
1029
+ ]);
1030
+ goProfile = {
1031
+ id: "go",
1032
+ displayName: "Go",
1033
+ extensions: Object.freeze([".go"]),
1034
+ lspLanguageIds: Object.freeze(["go"]),
1035
+ detectors: Object.freeze([
1036
+ { kind: "manifest", filename: "go.mod", weight: 90 },
1037
+ { kind: "manifest", filename: "go.work", weight: 95 },
1038
+ { kind: "lockfile", filename: "go.sum", weight: 30 }
1039
+ ]),
1040
+ ignoredDirectories: COMMON_IGNORES,
1041
+ packageManagers: Object.freeze(["go"]),
1042
+ executables: Object.freeze(["go", "gofmt"]),
1043
+ operations: Object.freeze({
1044
+ syntax: async (ctx) => {
1045
+ if (!ctx.target)
1046
+ return unavailable(ctx, "syntax", "Go syntax planning requires a target file.");
1047
+ return processPlan(ctx, "syntax", "gofmt", ["-e", "-d", ctx.target], {
1048
+ parser: "gofmt",
1049
+ reason: "Parse the target and report syntax errors without writing it."
1050
+ });
1051
+ },
1052
+ semantic: async (ctx) => processPlan(ctx, "semantic", "go", ["test", "-run", "^$", "./..."], {
1053
+ parser: "go-test",
1054
+ reason: "Compile all Go packages without selecting tests.",
1055
+ mutating: true,
1056
+ executesProjectCode: true
1057
+ }),
1058
+ lint: async (ctx) => processPlan(ctx, "lint", "go", ["vet", "./..."], {
1059
+ parser: "go-compiler",
1060
+ reason: "Run the standard Go vet checks.",
1061
+ mutating: true,
1062
+ executesProjectCode: true
1063
+ }),
1064
+ "format-check": async (ctx) => ctx.target ? processPlan(ctx, "format-check", "gofmt", ["-d", ctx.target], {
1065
+ parser: "gofmt",
1066
+ reason: "Report Go formatting differences for the target without writing it."
1067
+ }) : unavailable(ctx, "format-check", "Go formatting requires an explicit target file."),
1068
+ "format-write": async (ctx) => ctx.target ? processPlan(ctx, "format-write", "gofmt", ["-w", ctx.target], {
1069
+ parser: "gofmt",
1070
+ reason: "Format the target Go source file.",
1071
+ mutating: true
1072
+ }) : unavailable(ctx, "format-write", "Go formatting requires an explicit target file."),
1073
+ test: async (ctx) => processPlan(
1074
+ ctx,
1075
+ "test",
1076
+ "go",
1077
+ ["test", ...ctx.options.filter ? ["-run", ctx.options.filter] : [], "./..."],
1078
+ {
1079
+ parser: "go-test",
1080
+ reason: ctx.options.filter ? "Run filtered Go tests." : "Run all Go tests.",
1081
+ mutating: true,
1082
+ executesProjectCode: true
1083
+ }
1084
+ ),
1085
+ build: async (ctx) => processPlan(ctx, "build", "go", ["build", "./..."], {
1086
+ parser: "go-compiler",
1087
+ reason: "Build all Go packages.",
1088
+ mutating: true,
1089
+ executesProjectCode: true
1090
+ }),
1091
+ run: async (ctx) => processPlan(ctx, "run", "go", ["run", "."], {
1092
+ parser: "command-text",
1093
+ reason: "Run the Go module entry point.",
1094
+ executesProjectCode: true
1095
+ }),
1096
+ "debug-race": async (ctx) => processPlan(ctx, "debug-race", "go", ["test", "-race", "./..."], {
1097
+ parser: "go-test",
1098
+ reason: "Collect Go race-detector evidence.",
1099
+ mutating: true,
1100
+ executesProjectCode: true
1101
+ }),
1102
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "go", ["mod", "download"], {
1103
+ parser: "go-module",
1104
+ reason: "Download the dependencies declared by go.mod.",
1105
+ mutating: true,
1106
+ network: true
1107
+ }),
1108
+ "package-add": async (ctx) => {
1109
+ const names = packageNames(ctx);
1110
+ if (names.length === 0)
1111
+ return unavailable(ctx, "package-add", "At least one Go module is required.");
1112
+ return processPlan(ctx, "package-add", "go", ["get", ...names], {
1113
+ parser: "go-module",
1114
+ reason: "Add validated Go modules.",
1115
+ mutating: true,
1116
+ network: true
1117
+ });
1118
+ },
1119
+ "package-update": async (ctx) => processPlan(ctx, "package-update", "go", ["get", "-u", "./..."], {
1120
+ parser: "go-module",
1121
+ reason: "Update dependencies of all Go packages.",
1122
+ mutating: true,
1123
+ network: true
1124
+ })
1125
+ })
1126
+ };
1127
+ rustProfile = {
1128
+ id: "rust",
1129
+ displayName: "Rust",
1130
+ extensions: Object.freeze([".rs"]),
1131
+ lspLanguageIds: Object.freeze(["rust"]),
1132
+ detectors: Object.freeze([
1133
+ { kind: "manifest", filename: "Cargo.toml", weight: 90 },
1134
+ { kind: "lockfile", filename: "Cargo.lock", weight: 30 }
1135
+ ]),
1136
+ ignoredDirectories: COMMON_IGNORES,
1137
+ packageManagers: Object.freeze(["cargo"]),
1138
+ executables: Object.freeze(["cargo"]),
1139
+ operations: Object.freeze({
1140
+ syntax: async (ctx) => processPlan(ctx, "syntax", "cargo", ["check", "--message-format=json"], {
1141
+ parser: "cargo-json",
1142
+ reason: "Check Rust syntax and semantics with Cargo.",
1143
+ mutating: true,
1144
+ executesProjectCode: true
1145
+ }),
1146
+ semantic: async (ctx) => processPlan(ctx, "semantic", "cargo", ["check", "--message-format=json"], {
1147
+ parser: "cargo-json",
1148
+ reason: "Collect Rust compiler diagnostics with Cargo check.",
1149
+ mutating: true,
1150
+ executesProjectCode: true
1151
+ }),
1152
+ lint: async (ctx) => processPlan(ctx, "lint", "cargo", ["clippy", "--message-format=json"], {
1153
+ parser: "cargo-json",
1154
+ reason: "Run Clippy for this crate.",
1155
+ mutating: true,
1156
+ executesProjectCode: true
1157
+ }),
1158
+ "format-check": async (ctx) => processPlan(ctx, "format-check", "cargo", ["fmt", "--check"], {
1159
+ parser: "cargo-fmt",
1160
+ reason: "Check Rust formatting without writing files."
1161
+ }),
1162
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "cargo", ["fmt"], {
1163
+ parser: "cargo-fmt",
1164
+ reason: "Format Rust source files in the workspace.",
1165
+ mutating: true
1166
+ }),
1167
+ "test-compile": async (ctx) => processPlan(ctx, "test-compile", "cargo", ["test", "--no-run", "--message-format=json"], {
1168
+ parser: "cargo-json",
1169
+ reason: "Compile Rust tests without running them.",
1170
+ mutating: true,
1171
+ executesProjectCode: true
1172
+ }),
1173
+ test: async (ctx) => processPlan(
1174
+ ctx,
1175
+ "test",
1176
+ "cargo",
1177
+ ["test", ...ctx.options.filter ? [ctx.options.filter] : []],
1178
+ {
1179
+ parser: "cargo-test",
1180
+ reason: ctx.options.filter ? "Run filtered Rust tests." : "Run Rust tests.",
1181
+ mutating: true,
1182
+ executesProjectCode: true
1183
+ }
1184
+ ),
1185
+ build: async (ctx) => processPlan(ctx, "build", "cargo", ["build", "--message-format=json"], {
1186
+ parser: "cargo-json",
1187
+ reason: "Build the Rust workspace.",
1188
+ mutating: true,
1189
+ executesProjectCode: true
1190
+ }),
1191
+ run: async (ctx) => processPlan(ctx, "run", "cargo", ["run"], {
1192
+ parser: "command-text",
1193
+ reason: "Run the Rust workspace entry point.",
1194
+ executesProjectCode: true
1195
+ }),
1196
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "cargo", ["fetch", "--locked"], {
1197
+ parser: "cargo-json",
1198
+ reason: "Fetch locked Rust dependencies.",
1199
+ mutating: true,
1200
+ network: true
1201
+ }),
1202
+ "package-add": async (ctx) => {
1203
+ const names = packageNames(ctx);
1204
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one crate is required.") : processPlan(ctx, "package-add", "cargo", ["add", ...names], {
1205
+ parser: "cargo-text",
1206
+ reason: "Add validated Rust crates.",
1207
+ mutating: true,
1208
+ network: true
1209
+ });
1210
+ },
1211
+ "package-remove": async (ctx) => {
1212
+ const names = packageNames(ctx);
1213
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one crate is required.") : processPlan(ctx, "package-remove", "cargo", ["remove", ...names], {
1214
+ parser: "cargo-text",
1215
+ reason: "Remove validated Rust crates.",
1216
+ mutating: true
1217
+ });
1218
+ },
1219
+ "package-update": async (ctx) => processPlan(ctx, "package-update", "cargo", ["update"], {
1220
+ parser: "cargo-text",
1221
+ reason: "Update the Cargo lockfile.",
1222
+ mutating: true,
1223
+ network: true
1224
+ }),
1225
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "cargo", ["audit", "--json"], {
1226
+ parser: "cargo-audit",
1227
+ reason: "Audit Rust dependencies when cargo-audit is installed.",
1228
+ network: true
1229
+ })
1230
+ })
1231
+ };
1232
+ phpProfile = {
1233
+ id: "php",
1234
+ displayName: "PHP",
1235
+ extensions: Object.freeze([".php"]),
1236
+ lspLanguageIds: Object.freeze(["php"]),
1237
+ detectors: Object.freeze([
1238
+ { kind: "manifest", filename: "composer.json", weight: 90 },
1239
+ { kind: "lockfile", filename: "composer.lock", weight: 30 },
1240
+ { kind: "config", filename: "phpunit.xml", weight: 25 },
1241
+ { kind: "config", filename: "phpunit.xml.dist", weight: 25 }
1242
+ ]),
1243
+ ignoredDirectories: COMMON_IGNORES,
1244
+ packageManagers: Object.freeze(["composer"]),
1245
+ executables: Object.freeze(["php", "composer"]),
1246
+ operations: Object.freeze({
1247
+ syntax: async (ctx) => ctx.target ? processPlan(ctx, "syntax", "php", ["-l", ctx.target], {
1248
+ parser: "php-lint",
1249
+ reason: "Lint the target PHP file without executing it."
1250
+ }) : unavailable(ctx, "syntax", "PHP syntax planning requires a target file."),
1251
+ semantic: async (ctx) => unavailable(
1252
+ ctx,
1253
+ "semantic",
1254
+ "No configured PHPStan or Psalm adapter was detected in Phase 1."
1255
+ ),
1256
+ test: async (ctx) => processPlan(
1257
+ ctx,
1258
+ "test",
1259
+ "php",
1260
+ ["vendor/bin/phpunit", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
1261
+ {
1262
+ parser: "phpunit",
1263
+ reason: ctx.options.filter ? "Run filtered tests with the project-local PHPUnit runner." : "Run the project-local PHPUnit test runner.",
1264
+ executesProjectCode: true
1265
+ }
1266
+ ),
1267
+ "package-install": async (ctx) => processPlan(
1268
+ ctx,
1269
+ "package-install",
1270
+ "composer",
1271
+ ["install", "--no-interaction", "--no-scripts"],
1272
+ {
1273
+ parser: "composer",
1274
+ reason: "Restore Composer dependencies without scripts.",
1275
+ mutating: true,
1276
+ network: true
1277
+ }
1278
+ ),
1279
+ "package-add": async (ctx) => {
1280
+ const names = packageNames(ctx);
1281
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one Composer package is required.") : processPlan(
1282
+ ctx,
1283
+ "package-add",
1284
+ "composer",
1285
+ ["require", "--no-interaction", "--no-scripts", ...names],
1286
+ {
1287
+ parser: "composer",
1288
+ reason: "Add validated Composer packages without scripts.",
1289
+ mutating: true,
1290
+ network: true
1291
+ }
1292
+ );
1293
+ },
1294
+ "package-remove": async (ctx) => {
1295
+ const names = packageNames(ctx);
1296
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one Composer package is required.") : processPlan(
1297
+ ctx,
1298
+ "package-remove",
1299
+ "composer",
1300
+ ["remove", "--no-interaction", "--no-scripts", ...names],
1301
+ {
1302
+ parser: "composer",
1303
+ reason: "Remove validated Composer packages without scripts.",
1304
+ mutating: true
1305
+ }
1306
+ );
1307
+ },
1308
+ "package-update": async (ctx) => processPlan(
1309
+ ctx,
1310
+ "package-update",
1311
+ "composer",
1312
+ ["update", "--no-interaction", "--no-scripts"],
1313
+ {
1314
+ parser: "composer",
1315
+ reason: "Update Composer dependencies without scripts.",
1316
+ mutating: true,
1317
+ network: true
1318
+ }
1319
+ ),
1320
+ "package-audit": async (ctx) => processPlan(ctx, "package-audit", "composer", ["audit", "--format=json"], {
1321
+ parser: "composer-audit",
1322
+ reason: "Audit Composer dependencies.",
1323
+ network: true
1324
+ }),
1325
+ "package-outdated": async (ctx) => processPlan(ctx, "package-outdated", "composer", ["outdated", "--format=json"], {
1326
+ parser: "composer-outdated",
1327
+ reason: "Check outdated Composer dependencies.",
1328
+ network: true
1329
+ })
1330
+ })
1331
+ };
1332
+ csharpProfile = {
1333
+ id: "csharp",
1334
+ displayName: "C# / .NET",
1335
+ extensions: Object.freeze([".cs"]),
1336
+ lspLanguageIds: Object.freeze(["csharp"]),
1337
+ detectors: Object.freeze([
1338
+ { kind: "config", filename: "global.json", weight: 25 },
1339
+ { kind: "manifest", suffix: ".slnx", weight: 95 },
1340
+ { kind: "manifest", suffix: ".sln", weight: 95 },
1341
+ { kind: "manifest", suffix: ".csproj", weight: 90 },
1342
+ { kind: "manifest", suffix: ".fsproj", weight: 90 },
1343
+ { kind: "lockfile", filename: "packages.lock.json", weight: 30 }
1344
+ ]),
1345
+ ignoredDirectories: COMMON_IGNORES,
1346
+ packageManagers: Object.freeze(["dotnet"]),
1347
+ executables: Object.freeze(["dotnet"]),
1348
+ operations: Object.freeze({
1349
+ syntax: async (ctx) => processPlan(ctx, "syntax", "dotnet", ["build", "--no-restore"], {
1350
+ parser: "dotnet-build",
1351
+ reason: "Use the nearest project or solution to collect C# syntax diagnostics.",
1352
+ mutating: true,
1353
+ executesProjectCode: true
1354
+ }),
1355
+ semantic: async (ctx) => processPlan(ctx, "semantic", "dotnet", ["build", "--no-restore"], {
1356
+ parser: "dotnet-build",
1357
+ reason: "Build without restoring to collect .NET compiler diagnostics.",
1358
+ mutating: true,
1359
+ executesProjectCode: true
1360
+ }),
1361
+ lint: async (ctx) => processPlan(ctx, "lint", "dotnet", ["format", "--verify-no-changes", "--no-restore"], {
1362
+ parser: "dotnet-format",
1363
+ reason: "Verify .NET formatting and analyzers without writing source files.",
1364
+ mutating: true,
1365
+ executesProjectCode: true
1366
+ }),
1367
+ "format-check": async (ctx) => processPlan(
1368
+ ctx,
1369
+ "format-check",
1370
+ "dotnet",
1371
+ ["format", "--verify-no-changes", "--no-restore"],
1372
+ {
1373
+ parser: "dotnet-format",
1374
+ reason: "Verify .NET formatting without source writes.",
1375
+ mutating: true,
1376
+ executesProjectCode: true
1377
+ }
1378
+ ),
1379
+ "format-write": async (ctx) => processPlan(ctx, "format-write", "dotnet", ["format", "--no-restore"], {
1380
+ parser: "dotnet-format",
1381
+ reason: "Format .NET source files without restoring packages.",
1382
+ mutating: true,
1383
+ executesProjectCode: true
1384
+ }),
1385
+ test: async (ctx) => processPlan(
1386
+ ctx,
1387
+ "test",
1388
+ "dotnet",
1389
+ ["test", "--no-restore", ...ctx.options.filter ? ["--filter", ctx.options.filter] : []],
1390
+ {
1391
+ parser: "dotnet-test",
1392
+ reason: ctx.options.filter ? "Run filtered .NET tests without restoring packages." : "Run .NET tests without restoring packages.",
1393
+ mutating: true,
1394
+ executesProjectCode: true
1395
+ }
1396
+ ),
1397
+ build: async (ctx) => processPlan(ctx, "build", "dotnet", ["build", "--no-restore"], {
1398
+ parser: "dotnet-build",
1399
+ reason: "Build the nearest .NET project or solution without restoring packages.",
1400
+ mutating: true,
1401
+ executesProjectCode: true
1402
+ }),
1403
+ run: async (ctx) => processPlan(ctx, "run", "dotnet", ["run", "--no-restore"], {
1404
+ parser: "command-text",
1405
+ reason: "Run the .NET project entry point.",
1406
+ mutating: true,
1407
+ executesProjectCode: true
1408
+ }),
1409
+ "package-install": async (ctx) => processPlan(ctx, "package-install", "dotnet", ["restore", "--locked-mode"], {
1410
+ parser: "dotnet-restore",
1411
+ reason: "Restore locked .NET dependencies.",
1412
+ mutating: true,
1413
+ network: true,
1414
+ executesProjectCode: true
1415
+ }),
1416
+ "package-add": async (ctx) => {
1417
+ const names = packageNames(ctx);
1418
+ return names.length === 0 ? unavailable(ctx, "package-add", "At least one NuGet package is required.") : processPlan(ctx, "package-add", "dotnet", ["add", "package", ...names], {
1419
+ parser: "dotnet-package",
1420
+ reason: "Add validated NuGet packages.",
1421
+ mutating: true,
1422
+ network: true,
1423
+ executesProjectCode: true
1424
+ });
1425
+ },
1426
+ "package-remove": async (ctx) => {
1427
+ const names = packageNames(ctx);
1428
+ return names.length === 0 ? unavailable(ctx, "package-remove", "At least one NuGet package is required.") : processPlan(ctx, "package-remove", "dotnet", ["remove", "package", ...names], {
1429
+ parser: "dotnet-package",
1430
+ reason: "Remove validated NuGet packages.",
1431
+ mutating: true
1432
+ });
1433
+ },
1434
+ "package-audit": async (ctx) => processPlan(
1435
+ ctx,
1436
+ "package-audit",
1437
+ "dotnet",
1438
+ ["list", "package", "--vulnerable", "--format", "json"],
1439
+ { parser: "dotnet-package", reason: "List vulnerable NuGet dependencies.", network: true }
1440
+ ),
1441
+ "package-outdated": async (ctx) => processPlan(
1442
+ ctx,
1443
+ "package-outdated",
1444
+ "dotnet",
1445
+ ["list", "package", "--outdated", "--format", "json"],
1446
+ { parser: "dotnet-package", reason: "List outdated NuGet dependencies.", network: true }
1447
+ )
1448
+ })
1449
+ };
1450
+ PRIMARY_LANGUAGE_PROFILES = Object.freeze([
1451
+ Object.freeze(typescriptProfile()),
1452
+ Object.freeze(javascriptProfile()),
1453
+ Object.freeze(goProfile),
1454
+ Object.freeze(rustProfile),
1455
+ Object.freeze(phpProfile),
1456
+ Object.freeze(csharpProfile)
1457
+ ]);
1458
+ }
1459
+ });
1460
+
1461
+ // src/languages/registry.ts
1462
+ function validateLanguageProfile(profile) {
1463
+ const errors = [];
1464
+ if (!PROFILE_ID_RE.test(profile.id)) errors.push("id must be a lowercase stable identifier");
1465
+ if (!profile.displayName.trim()) errors.push("displayName is required");
1466
+ if (profile.extensions.length === 0) errors.push("at least one extension is required");
1467
+ for (const ext of profile.extensions) {
1468
+ if (!/^\.[a-z0-9+.-]+$/i.test(ext)) errors.push(`invalid extension "${ext}"`);
1469
+ }
1470
+ if (profile.detectors.length === 0) errors.push("at least one detector is required");
1471
+ for (const detector of profile.detectors) {
1472
+ if ((detector.filename ? 1 : 0) + (detector.suffix ? 1 : 0) !== 1) {
1473
+ errors.push("each detector must declare exactly one of filename or suffix");
1474
+ }
1475
+ if (!Number.isFinite(detector.weight) || detector.weight <= 0 || detector.weight > 100) {
1476
+ errors.push("detector weights must be in the range 1..100");
1477
+ }
1478
+ const marker = detector.filename ?? detector.suffix ?? "";
1479
+ if (marker.includes("/") || marker.includes("\\") || marker.includes("\0")) {
1480
+ errors.push(`detector marker "${marker}" must be a basename or suffix`);
1481
+ }
1482
+ }
1483
+ if (profile.executables.length === 0) errors.push("at least one executable is required");
1484
+ for (const executable of profile.executables) {
1485
+ if (!EXECUTABLE_RE.test(executable)) errors.push(`invalid executable token "${executable}"`);
1486
+ }
1487
+ for (const operation of Object.keys(profile.operations)) {
1488
+ if (!KNOWN_OPERATIONS.has(operation)) {
1489
+ errors.push(`unknown operation "${operation}"`);
1490
+ }
1491
+ if (typeof profile.operations[operation] !== "function") {
1492
+ errors.push(`operation "${operation}" must be a resolver function`);
1493
+ }
1494
+ }
1495
+ return [...new Set(errors)];
1496
+ }
1497
+ function freezeProfile(profile) {
1498
+ const detectors = Object.freeze(profile.detectors.map((item) => Object.freeze({ ...item })));
1499
+ const operations = Object.freeze({ ...profile.operations });
1500
+ return Object.freeze({
1501
+ ...profile,
1502
+ extensions: Object.freeze([...profile.extensions]),
1503
+ lspLanguageIds: Object.freeze([...profile.lspLanguageIds]),
1504
+ detectors,
1505
+ ignoredDirectories: Object.freeze([...profile.ignoredDirectories]),
1506
+ packageManagers: Object.freeze([...profile.packageManagers]),
1507
+ executables: Object.freeze([...profile.executables]),
1508
+ operations
1509
+ });
1510
+ }
1511
+ var PROFILE_ID_RE, EXECUTABLE_RE, KNOWN_OPERATIONS, LanguageProfileRegistry, languageProfileRegistry;
1512
+ var init_registry = __esm({
1513
+ "src/languages/registry.ts"() {
1514
+ "use strict";
1515
+ init_additional();
1516
+ init_primary();
1517
+ PROFILE_ID_RE = /^[a-z][a-z0-9-]{0,63}$/;
1518
+ EXECUTABLE_RE = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/;
1519
+ KNOWN_OPERATIONS = /* @__PURE__ */ new Set([
1520
+ "syntax",
1521
+ "semantic",
1522
+ "lint",
1523
+ "format-check",
1524
+ "format-write",
1525
+ "test-compile",
1526
+ "test",
1527
+ "build",
1528
+ "run",
1529
+ "debug-compile",
1530
+ "debug-test",
1531
+ "debug-runtime",
1532
+ "debug-race",
1533
+ "package-install",
1534
+ "package-add",
1535
+ "package-remove",
1536
+ "package-update",
1537
+ "package-audit",
1538
+ "package-outdated"
1539
+ ]);
1540
+ LanguageProfileRegistry = class {
1541
+ #profiles = /* @__PURE__ */ new Map();
1542
+ constructor(profiles = []) {
1543
+ for (const profile of profiles) this.register(profile);
1544
+ }
1545
+ register(profile) {
1546
+ const errors = validateLanguageProfile(profile);
1547
+ if (errors.length > 0) {
1548
+ throw new Error(`Invalid language profile "${profile.id}": ${errors.join("; ")}`);
1549
+ }
1550
+ if (this.#profiles.has(profile.id)) {
1551
+ throw new Error(`Language profile "${profile.id}" is already registered`);
1552
+ }
1553
+ this.#profiles.set(profile.id, freezeProfile(profile));
1554
+ }
1555
+ get(id) {
1556
+ return this.#profiles.get(id);
1557
+ }
1558
+ list() {
1559
+ return Object.freeze([...this.#profiles.values()]);
1560
+ }
1561
+ };
1562
+ languageProfileRegistry = new LanguageProfileRegistry([
1563
+ ...PRIMARY_LANGUAGE_PROFILES,
1564
+ ...ADDITIONAL_LANGUAGE_PROFILES
1565
+ ]);
1566
+ }
1567
+ });
1568
+
1569
+ // src/languages/detect.ts
1570
+ import { createHash } from "node:crypto";
1571
+ import * as fs2 from "node:fs/promises";
1572
+ import * as path3 from "node:path";
1573
+ async function detectLanguageWorkspaces(options) {
1574
+ const projectRoot = await canonicalDirectory(options.projectRoot);
1575
+ const cwdInput = options.cwd ? path3.isAbsolute(options.cwd) ? options.cwd : path3.resolve(projectRoot, options.cwd) : projectRoot;
1576
+ const cwd = await canonicalInside(cwdInput, projectRoot, "cwd");
1577
+ const target = options.target ? await canonicalInside(resolveFrom(cwd, options.target), projectRoot, "target") : void 0;
1578
+ const profiles = (options.profiles ?? languageProfileRegistry.list()).filter((profile) => !options.language || profile.id === options.language).slice().sort((a, b) => a.id.localeCompare(b.id));
1579
+ const limits = normalizeLimits(options.limits);
1580
+ const state = {
1581
+ entries: 0,
1582
+ truncated: false,
1583
+ sourcePaths: /* @__PURE__ */ new Map(),
1584
+ candidates: /* @__PURE__ */ new Map()
1585
+ };
1586
+ await scanDirectory(projectRoot, 0, profiles, limits, state, options.signal);
1587
+ addSourceFallbacks(projectRoot, profiles, state);
1588
+ if (target) addTargetEvidence(target, projectRoot, profiles, state);
1589
+ const workspaces = await Promise.all(
1590
+ [...state.candidates.values()].map((candidate) => finalizeCandidate(candidate, projectRoot))
1591
+ );
1592
+ workspaces.sort(compareWorkspaces);
1593
+ return {
1594
+ projectRoot,
1595
+ scannedEntries: state.entries,
1596
+ truncated: state.truncated,
1597
+ workspaces
1598
+ };
1599
+ }
1600
+ async function scanDirectory(directory, depth, profiles, limits, state, signal) {
1601
+ signal?.throwIfAborted();
1602
+ if (depth > limits.maxDepth || state.entries >= limits.maxEntries) {
1603
+ state.truncated = true;
1604
+ return;
1605
+ }
1606
+ let entries;
1607
+ try {
1608
+ entries = await fs2.readdir(directory, { withFileTypes: true });
1609
+ } catch {
1610
+ return;
1611
+ }
1612
+ entries.sort((a, b) => a.name.localeCompare(b.name));
1613
+ for (const entry of entries) {
1614
+ signal?.throwIfAborted();
1615
+ if (state.entries >= limits.maxEntries) {
1616
+ state.truncated = true;
1617
+ return;
1618
+ }
1619
+ state.entries++;
1620
+ const fullPath = path3.join(directory, entry.name);
1621
+ if (entry.isSymbolicLink()) continue;
1622
+ if (entry.isDirectory()) {
1623
+ if (shouldIgnoreDirectory(entry.name, profiles)) continue;
1624
+ if (depth >= limits.maxDepth) {
1625
+ state.truncated = true;
1626
+ continue;
1627
+ }
1628
+ await scanDirectory(fullPath, depth + 1, profiles, limits, state, signal);
1629
+ continue;
1630
+ }
1631
+ if (!entry.isFile()) continue;
1632
+ collectFileEvidence(directory, fullPath, entry.name, profiles, state);
1633
+ }
1634
+ }
1635
+ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1636
+ const lower = basename4.toLowerCase();
1637
+ const extension = path3.extname(lower);
1638
+ for (const profile of profiles) {
1639
+ const detector = profile.detectors.find(
1640
+ (rule) => rule.filename ? lower === rule.filename.toLowerCase() : lower.endsWith(rule.suffix.toLowerCase())
1641
+ );
1642
+ if (detector) {
1643
+ const candidate = getCandidate(state, profile, directory);
1644
+ candidate.evidence.push({
1645
+ kind: detector.kind,
1646
+ path: fullPath,
1647
+ value: basename4,
1648
+ weight: detector.weight
1649
+ });
1650
+ if (detector.kind === "manifest" || detector.kind === "config") {
1651
+ candidate.manifests.push(fullPath);
1652
+ }
1653
+ }
1654
+ if (profile.extensions.includes(extension)) {
1655
+ const paths = state.sourcePaths.get(profile.id) ?? [];
1656
+ if (paths.length < SOURCE_WEIGHT_CAP / SOURCE_WEIGHT) paths.push(fullPath);
1657
+ state.sourcePaths.set(profile.id, paths);
1658
+ }
1659
+ }
1660
+ }
1661
+ function addSourceFallbacks(projectRoot, profiles, state) {
1662
+ for (const profile of profiles) {
1663
+ const sources = state.sourcePaths.get(profile.id) ?? [];
1664
+ if (sources.length === 0) continue;
1665
+ const profileCandidates = [...state.candidates.values()].filter(
1666
+ (item) => item.profile.id === profile.id
1667
+ );
1668
+ for (const source of sources) {
1669
+ const containing = profileCandidates.filter((candidate2) => isInside(source, candidate2.root)).sort(
1670
+ (a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
1671
+ );
1672
+ const candidate = containing[0] ?? (profile.sourceFallback === false ? void 0 : getCandidate(state, profile, projectRoot));
1673
+ if (!candidate) continue;
1674
+ candidate.evidence.push({
1675
+ kind: "source",
1676
+ path: source,
1677
+ value: path3.extname(source),
1678
+ weight: SOURCE_WEIGHT
1679
+ });
1680
+ }
1681
+ }
1682
+ }
1683
+ function addTargetEvidence(target, projectRoot, profiles, state) {
1684
+ const extension = path3.extname(target).toLowerCase();
1685
+ for (const profile of profiles) {
1686
+ if (!profile.extensions.includes(extension)) continue;
1687
+ const candidates = [...state.candidates.values()].filter(
1688
+ (candidate2) => candidate2.profile.id === profile.id && isInside(target, candidate2.root)
1689
+ );
1690
+ const candidate = candidates.length > 0 ? candidates.sort(
1691
+ (a, b) => pathDepth(b.root, projectRoot) - pathDepth(a.root, projectRoot) || a.root.localeCompare(b.root)
1692
+ )[0] : profile.sourceFallback === false ? void 0 : getCandidate(state, profile, path3.dirname(target));
1693
+ if (!candidate) continue;
1694
+ candidate.evidence.push({
1695
+ kind: "target",
1696
+ path: target,
1697
+ value: extension,
1698
+ weight: TARGET_WEIGHT
1699
+ });
1700
+ }
1701
+ }
1702
+ async function finalizeCandidate(candidate, projectRoot) {
1703
+ const evidence = dedupeEvidence(candidate.evidence).sort(compareEvidence);
1704
+ const manifests = [...new Set(candidate.manifests)].sort();
1705
+ const confidence = Math.min(1, evidence.reduce((sum, item) => sum + item.weight, 0) / 100);
1706
+ const packageManager = await detectPackageManager2(candidate.profile, candidate.root, evidence);
1707
+ const id = createHash("sha256").update(`${candidate.profile.id}\0${path3.relative(projectRoot, candidate.root)}`).digest("hex").slice(0, 16);
1708
+ return Object.freeze({
1709
+ id,
1710
+ language: candidate.profile.id,
1711
+ root: candidate.root,
1712
+ confidence,
1713
+ evidence: Object.freeze(evidence.map((item) => Object.freeze(item))),
1714
+ ...packageManager ? { packageManager } : {},
1715
+ manifests: Object.freeze(manifests),
1716
+ capabilities: Object.freeze(
1717
+ Object.keys(candidate.profile.operations).sort()
1718
+ )
1719
+ });
1720
+ }
1721
+ async function detectPackageManager2(profile, root, evidence) {
1722
+ if (profile.packageManagers.length === 1) return profile.packageManagers[0];
1723
+ if (profile.id !== "typescript" && profile.id !== "javascript") return void 0;
1724
+ let declared;
1725
+ try {
1726
+ const pkg = JSON.parse(await fs2.readFile(path3.join(root, "package.json"), "utf8"));
1727
+ if (typeof pkg.packageManager === "string") {
1728
+ const manager = pkg.packageManager.split("@")[0];
1729
+ if (manager && profile.packageManagers.includes(manager)) declared = manager;
1730
+ }
1731
+ } catch {
1732
+ }
1733
+ const lockManagers = /* @__PURE__ */ new Set();
1734
+ for (const item of evidence) {
1735
+ const name = path3.basename(item.path).toLowerCase();
1736
+ if (name === "pnpm-lock.yaml") lockManagers.add("pnpm");
1737
+ else if (name === "yarn.lock") lockManagers.add("yarn");
1738
+ else if (name === "bun.lock" || name === "bun.lockb") lockManagers.add("bun");
1739
+ else if (name === "package-lock.json") lockManagers.add("npm");
1740
+ }
1741
+ if (declared && (lockManagers.size === 0 || lockManagers.size === 1 && lockManagers.has(declared))) {
1742
+ return declared;
1743
+ }
1744
+ if (lockManagers.size === 1) return [...lockManagers][0];
1745
+ if (lockManagers.size > 1) return void 0;
1746
+ return declared ?? "npm";
1747
+ }
1748
+ function getCandidate(state, profile, root) {
1749
+ const key = `${profile.id}\0${root}`;
1750
+ let candidate = state.candidates.get(key);
1751
+ if (!candidate) {
1752
+ candidate = { profile, root, evidence: [], manifests: [] };
1753
+ state.candidates.set(key, candidate);
1754
+ }
1755
+ return candidate;
1756
+ }
1757
+ function shouldIgnoreDirectory(name, profiles) {
1758
+ if (GLOBAL_IGNORES.has(name) || name.startsWith(".")) return true;
1759
+ return profiles.some((profile) => profile.ignoredDirectories.includes(name));
1760
+ }
1761
+ function normalizeLimits(input) {
1762
+ const maxDepth = Math.max(
1763
+ 0,
1764
+ Math.min(12, Math.trunc(input?.maxDepth ?? DEFAULT_LIMITS.maxDepth))
1765
+ );
1766
+ const maxEntries = Math.max(
1767
+ 1,
1768
+ Math.min(5e4, Math.trunc(input?.maxEntries ?? DEFAULT_LIMITS.maxEntries))
1769
+ );
1770
+ return { maxDepth, maxEntries };
1771
+ }
1772
+ async function canonicalDirectory(input) {
1773
+ const resolved = path3.resolve(input);
1774
+ const real = await fs2.realpath(resolved);
1775
+ const stat5 = await fs2.stat(real);
1776
+ if (!stat5.isDirectory()) throw new Error(`Project root is not a directory: ${input}`);
1777
+ return real;
1778
+ }
1779
+ async function canonicalInside(input, root, label) {
1780
+ const resolved = path3.resolve(input);
1781
+ let real;
1782
+ try {
1783
+ real = await fs2.realpath(resolved);
1784
+ } catch (error) {
1785
+ if (error.code !== "ENOENT") throw error;
1786
+ const parent = await fs2.realpath(path3.dirname(resolved));
1787
+ real = path3.join(parent, path3.basename(resolved));
1788
+ }
1789
+ if (!isInside(real, root)) throw new Error(`${label} is outside project root: ${input}`);
1790
+ return real;
1791
+ }
1792
+ function resolveFrom(cwd, input) {
1793
+ return path3.isAbsolute(input) ? input : path3.resolve(cwd, input);
1794
+ }
1795
+ function isInside(candidate, root) {
1796
+ const relative5 = path3.relative(root, candidate);
1797
+ return relative5 === "" || !relative5.startsWith("..") && !path3.isAbsolute(relative5);
1798
+ }
1799
+ function pathDepth(candidate, root) {
1800
+ const relative5 = path3.relative(root, candidate);
1801
+ return relative5 === "" ? 0 : relative5.split(path3.sep).length;
1802
+ }
1803
+ function dedupeEvidence(items) {
1804
+ const seen = /* @__PURE__ */ new Set();
1805
+ return items.filter((item) => {
1806
+ const key = `${item.kind}\0${item.path}\0${item.value}\0${item.weight}`;
1807
+ if (seen.has(key)) return false;
1808
+ seen.add(key);
1809
+ return true;
1810
+ });
1811
+ }
1812
+ function compareEvidence(a, b) {
1813
+ return a.path.localeCompare(b.path) || a.kind.localeCompare(b.kind) || b.weight - a.weight;
1814
+ }
1815
+ function compareWorkspaces(a, b) {
1816
+ return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root) || a.id.localeCompare(b.id);
1817
+ }
1818
+ var DEFAULT_LIMITS, SOURCE_WEIGHT, SOURCE_WEIGHT_CAP, TARGET_WEIGHT, GLOBAL_IGNORES;
1819
+ var init_detect = __esm({
1820
+ "src/languages/detect.ts"() {
1821
+ "use strict";
1822
+ init_registry();
1823
+ DEFAULT_LIMITS = { maxDepth: 6, maxEntries: 5e3 };
1824
+ SOURCE_WEIGHT = 5;
1825
+ SOURCE_WEIGHT_CAP = 25;
1826
+ TARGET_WEIGHT = 100;
1827
+ GLOBAL_IGNORES = /* @__PURE__ */ new Set([
1828
+ ".git",
1829
+ ".wrongstack",
1830
+ "node_modules",
1831
+ "vendor",
1832
+ "target",
1833
+ "bin",
1834
+ "obj",
1835
+ "dist",
1836
+ "build",
1837
+ "out",
1838
+ "coverage",
1839
+ ".cache",
1840
+ ".idea",
1841
+ ".vscode",
1842
+ ".next",
1843
+ ".nuxt"
1844
+ ]);
1845
+ }
1846
+ });
1847
+
1848
+ // src/languages/diagnostics.ts
1849
+ import * as path4 from "node:path";
1850
+ function parseLanguageDiagnostics(parser, stdout, stderr, workspaceRoot) {
1851
+ const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
1852
+ let diagnostics;
1853
+ switch (parser) {
1854
+ case "typescript":
1855
+ diagnostics = parseTypeScript(text, workspaceRoot);
1856
+ break;
1857
+ case "cargo-json":
1858
+ diagnostics = parseCargoJson(text, workspaceRoot);
1859
+ break;
1860
+ case "php-lint":
1861
+ diagnostics = parsePhpLint(text, workspaceRoot);
1862
+ break;
1863
+ case "dotnet-build":
1864
+ case "dotnet-format":
1865
+ case "dotnet-test":
1866
+ diagnostics = parseDotnet(text, workspaceRoot);
1867
+ break;
1868
+ case "go-test":
1869
+ case "go-compiler":
1870
+ case "gofmt":
1871
+ diagnostics = parseGo(text, workspaceRoot);
1872
+ break;
1873
+ case "biome":
1874
+ diagnostics = parseBiome(text, workspaceRoot);
1875
+ break;
1876
+ default:
1877
+ diagnostics = parseGeneric(text, parser, workspaceRoot);
1878
+ break;
1879
+ }
1880
+ const sorted = dedupeDiagnostics(diagnostics).sort(compareDiagnostics);
1881
+ const omitted = Math.max(0, sorted.length - MAX_DIAGNOSTICS);
1882
+ const kept = Object.freeze(sorted.slice(0, MAX_DIAGNOSTICS).map((item) => Object.freeze(item)));
1883
+ return {
1884
+ diagnostics: kept,
1885
+ omitted,
1886
+ summary: summarize(kept)
1887
+ };
1888
+ }
1889
+ function diagnosticsForInternalSyntax(language, target, sourceText) {
1890
+ if (language !== "typescript" && language !== "javascript") {
1891
+ return Promise.resolve({ diagnostics: [], omitted: 0, summary: emptySummary() });
1892
+ }
1893
+ return import("@typescript/typescript6").then((tsModule) => {
1894
+ const ts = tsModule.default ?? tsModule;
1895
+ const extension = path4.extname(target).toLowerCase();
1896
+ const scriptKind = extension === ".tsx" ? ts.ScriptKind.TSX : extension === ".ts" || extension === ".mts" || extension === ".cts" ? ts.ScriptKind.TS : ts.ScriptKind.JSX;
1897
+ const sourceFile = ts.createSourceFile(
1898
+ path4.basename(target),
1899
+ sourceText,
1900
+ ts.ScriptTarget.Latest,
1901
+ false,
1902
+ scriptKind
1903
+ );
1904
+ const native = sourceFile.parseDiagnostics ?? [];
1905
+ const diagnostics = native.map((diagnostic) => {
1906
+ const start = diagnostic.start ?? 0;
1907
+ const location = sourceFile.getLineAndCharacterOfPosition(start);
1908
+ return {
1909
+ severity: diagnostic.category === ts.DiagnosticCategory.Warning ? "warning" : "error",
1910
+ ...diagnostic.code ? { code: `TS${diagnostic.code}` } : {},
1911
+ message: ts.flattenDiagnosticMessageText(diagnostic.messageText, " "),
1912
+ file: target,
1913
+ range: { start: { line: location.line + 1, column: location.character + 1 } },
1914
+ source: "typescript-parser"
1915
+ };
1916
+ });
1917
+ const sorted = dedupeDiagnostics(diagnostics).sort(compareDiagnostics);
1918
+ const omitted = Math.max(0, sorted.length - MAX_DIAGNOSTICS);
1919
+ const kept = Object.freeze(sorted.slice(0, MAX_DIAGNOSTICS).map((item) => Object.freeze(item)));
1920
+ return { diagnostics: kept, omitted, summary: summarize(kept) };
1921
+ });
1922
+ }
1923
+ function parseTypeScript(text, root) {
1924
+ const diagnostics = [];
1925
+ const regex = /^(.+?)\((\d+),(\d+)\):\s+(error|warning)\s+(TS\d+):\s*(.+)$/gm;
1926
+ for (const match of text.matchAll(regex)) {
1927
+ diagnostics.push({
1928
+ severity: match[4] === "warning" ? "warning" : "error",
1929
+ code: match[5],
1930
+ message: match[6].trim(),
1931
+ file: normalizeDiagnosticPath(match[1], root),
1932
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
1933
+ source: "typescript"
1934
+ });
1935
+ }
1936
+ return diagnostics;
1937
+ }
1938
+ function parseCargoJson(text, root) {
1939
+ const diagnostics = [];
1940
+ for (const line of text.split(/\r?\n/)) {
1941
+ if (!line.trim().startsWith("{")) continue;
1942
+ try {
1943
+ const value = JSON.parse(line);
1944
+ if (value.reason !== "compiler-message" || !value.message?.message) continue;
1945
+ const primary = value.message.spans?.find((span) => span.is_primary) ?? value.message.spans?.[0];
1946
+ diagnostics.push({
1947
+ severity: normalizeSeverity(value.message.level),
1948
+ ...value.message.code?.code ? { code: value.message.code.code } : {},
1949
+ message: value.message.message,
1950
+ ...primary?.file_name ? { file: normalizeDiagnosticPath(primary.file_name, root) } : {},
1951
+ ...primary?.line_start ? { range: { start: { line: primary.line_start, column: primary.column_start ?? 1 } } } : {},
1952
+ source: "rustc"
1953
+ });
1954
+ } catch {
1955
+ }
1956
+ }
1957
+ return diagnostics;
1958
+ }
1959
+ function parseGo(text, root) {
1960
+ return parseLinePattern(
1961
+ text,
1962
+ /^(.*?\.go):(\d+):(\d+):\s*(.+)$/gm,
1963
+ root,
1964
+ "go",
1965
+ (_match, message) => ({ message, severity: /warning/i.test(message) ? "warning" : "error" })
1966
+ );
1967
+ }
1968
+ function parsePhpLint(text, root) {
1969
+ const diagnostics = [];
1970
+ const regex = /(?:PHP\s+)?(?:Parse|Fatal) error:\s*(.+?)\s+in\s+(.+?)\s+on line\s+(\d+)/gi;
1971
+ for (const match of text.matchAll(regex)) {
1972
+ diagnostics.push({
1973
+ severity: "error",
1974
+ message: match[1].trim(),
1975
+ file: normalizeDiagnosticPath(match[2], root),
1976
+ range: { start: { line: toPositiveInt(match[3]), column: 1 } },
1977
+ source: "php"
1978
+ });
1979
+ }
1980
+ return diagnostics;
1981
+ }
1982
+ function parseDotnet(text, root) {
1983
+ const diagnostics = [];
1984
+ const regex = /^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+([A-Z]+\d+):\s*(.+?)(?:\s+\[.+\])?$/gm;
1985
+ for (const match of text.matchAll(regex)) {
1986
+ diagnostics.push({
1987
+ severity: match[4] === "warning" ? "warning" : "error",
1988
+ code: match[5],
1989
+ message: match[6].trim(),
1990
+ file: normalizeDiagnosticPath(match[1], root),
1991
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
1992
+ source: "dotnet"
1993
+ });
1994
+ }
1995
+ return diagnostics;
1996
+ }
1997
+ function parseBiome(text, root) {
1998
+ const diagnostics = [];
1999
+ const regex = /^(.+?):(\d+):(\d+)\s+(lint\/[^\s]+|format)\s+(.+)$/gm;
2000
+ for (const match of text.matchAll(regex)) {
2001
+ diagnostics.push({
2002
+ severity: "warning",
2003
+ code: match[4],
2004
+ message: match[5].trim(),
2005
+ file: normalizeDiagnosticPath(match[1], root),
2006
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
2007
+ source: "biome"
2008
+ });
2009
+ }
2010
+ return diagnostics;
2011
+ }
2012
+ function parseGeneric(text, source, root) {
2013
+ return parseLinePattern(
2014
+ text,
2015
+ /^(.+?):(\d+):(\d+):\s*(?:(error|warning|info):\s*)?(.+)$/gm,
2016
+ root,
2017
+ source,
2018
+ (match, fallback) => ({
2019
+ severity: normalizeSeverity(match[4]),
2020
+ message: match[5]?.trim() || fallback
2021
+ })
2022
+ );
2023
+ }
2024
+ function parsePackageReports(parser, stdout, stderr) {
2025
+ const text = `${stdout}${stdout && stderr ? "\n" : ""}${stderr}`;
2026
+ switch (parser) {
2027
+ case "npm-audit":
2028
+ return parseNpmAudit(text);
2029
+ case "npm-outdated":
2030
+ return parseNpmOutdated(text);
2031
+ case "cargo-audit":
2032
+ return parseCargoAudit(text);
2033
+ case "composer-audit":
2034
+ return parseComposerAudit(text);
2035
+ case "composer-outdated":
2036
+ return parseComposerOutdated(text);
2037
+ case "dotnet-package":
2038
+ return parseDotnetPackage(text);
2039
+ default:
2040
+ return { diagnostics: [], vulnerabilities: [], outdated: [] };
2041
+ }
2042
+ }
2043
+ function parseNpmAudit(text) {
2044
+ const advisories = [];
2045
+ const diagnostics = [];
2046
+ let root;
2047
+ try {
2048
+ root = JSON.parse(text);
2049
+ } catch {
2050
+ return { diagnostics, vulnerabilities: advisories, outdated: [] };
2051
+ }
2052
+ const vulnerabilities = root?.vulnerabilities ?? {};
2053
+ const advisoriesRecord = root?.advisories ?? vulnerabilities;
2054
+ for (const [id, value] of Object.entries(advisoriesRecord)) {
2055
+ const advisory = value;
2056
+ const name = advisory.module_name ?? advisory.package_name ?? advisory.name ?? id;
2057
+ advisories.push({
2058
+ package: name,
2059
+ ...advisory.title ? { advisory: advisory.title } : { advisory: id },
2060
+ severity: mapSeverity(advisory.severity),
2061
+ ...advisory.patched_versions ? { fixedIn: advisory.patched_versions } : {},
2062
+ ...advisory.url ? { url: advisory.url } : {}
2063
+ });
2064
+ diagnostics.push({
2065
+ severity: mapSeverity(advisory.severity) === "critical" || mapSeverity(advisory.severity) === "high" ? "error" : "warning",
2066
+ code: id,
2067
+ message: advisory.title ?? `Vulnerability reported for ${name}.`,
2068
+ source: "npm-audit"
2069
+ });
2070
+ }
2071
+ return { diagnostics, vulnerabilities: advisories, outdated: [] };
2072
+ }
2073
+ function parseNpmOutdated(text) {
2074
+ const diagnostics = [];
2075
+ const outdated = [];
2076
+ let payload = {};
2077
+ try {
2078
+ payload = JSON.parse(text);
2079
+ } catch {
2080
+ return { diagnostics, vulnerabilities: [], outdated };
2081
+ }
2082
+ for (const [name, info] of Object.entries(payload)) {
2083
+ const entry = info;
2084
+ if (!entry.latest || entry.latest === entry.current) continue;
2085
+ outdated.push({
2086
+ name,
2087
+ previous: entry.current,
2088
+ resolved: entry.latest,
2089
+ kind: mapOutdatedKind(entry.type)
2090
+ });
2091
+ diagnostics.push({
2092
+ severity: "info",
2093
+ code: "outdated",
2094
+ message: `${name}: ${entry.current ?? "?"} \u2192 ${entry.latest}`,
2095
+ source: "npm-outdated"
2096
+ });
2097
+ }
2098
+ return { diagnostics, vulnerabilities: [], outdated };
2099
+ }
2100
+ function parseCargoAudit(text) {
2101
+ const diagnostics = [];
2102
+ const vulnerabilities = [];
2103
+ let root;
2104
+ try {
2105
+ root = JSON.parse(text);
2106
+ } catch {
2107
+ return { diagnostics, vulnerabilities, outdated: [] };
2108
+ }
2109
+ const findings = root.vulnerabilities?.found;
2110
+ if (!Array.isArray(findings)) return { diagnostics, vulnerabilities, outdated: [] };
2111
+ for (const finding of findings) {
2112
+ const item = finding;
2113
+ const name = item.package ?? "unknown";
2114
+ const advisory = item.id ?? item.advisory?.id ?? "cargo-audit";
2115
+ vulnerabilities.push({
2116
+ package: name,
2117
+ ...item.title ? { advisory: item.title } : { advisory },
2118
+ severity: mapSeverity(item.severity),
2119
+ ...item.patched_versions && item.patched_versions.length > 0 ? { fixedIn: item.patched_versions[0] } : {},
2120
+ ...item.url?.short ? { url: item.url.short } : {}
2121
+ });
2122
+ diagnostics.push({
2123
+ severity: mapSeverity(item.severity) === "critical" || mapSeverity(item.severity) === "high" ? "error" : "warning",
2124
+ code: advisory,
2125
+ message: item.title ?? `${name} reported by cargo-audit.`,
2126
+ source: "cargo-audit"
2127
+ });
2128
+ }
2129
+ return { diagnostics, vulnerabilities, outdated: [] };
2130
+ }
2131
+ function parseComposerAudit(text) {
2132
+ const diagnostics = [];
2133
+ const vulnerabilities = [];
2134
+ const lines = text.split(/\r?\n/);
2135
+ for (const line of lines) {
2136
+ if (!line.trim().startsWith("{")) continue;
2137
+ try {
2138
+ const entry = JSON.parse(line);
2139
+ if (!entry.package) continue;
2140
+ vulnerabilities.push({
2141
+ package: entry.package,
2142
+ ...entry.advisory ? { advisory: entry.advisory } : {},
2143
+ ...entry.title ? { advisory: entry.title } : { advisory: entry.advisory ?? "composer-audit" },
2144
+ severity: mapSeverity(entry.severity),
2145
+ ...entry.affectedVersions ? { fixedIn: entry.affectedVersions } : {},
2146
+ ...entry.url ? { url: entry.url } : {}
2147
+ });
2148
+ diagnostics.push({
2149
+ severity: "warning",
2150
+ code: entry.advisory ?? "composer-audit",
2151
+ message: entry.title ?? `${entry.package} reported by composer audit.`,
2152
+ source: "composer-audit"
2153
+ });
2154
+ } catch {
2155
+ }
2156
+ }
2157
+ return { diagnostics, vulnerabilities, outdated: [] };
2158
+ }
2159
+ function parseComposerOutdated(text) {
2160
+ const diagnostics = [];
2161
+ const outdated = [];
2162
+ const lines = text.split(/\r?\n/);
2163
+ for (const line of lines) {
2164
+ if (!line.trim().startsWith("{")) continue;
2165
+ try {
2166
+ const entry = JSON.parse(line);
2167
+ if (!entry.name || entry.version === entry.latest) continue;
2168
+ outdated.push({
2169
+ name: entry.name,
2170
+ previous: entry.version,
2171
+ resolved: entry.latest ?? entry.version
2172
+ });
2173
+ diagnostics.push({
2174
+ severity: "info",
2175
+ code: "outdated",
2176
+ message: `${entry.name}: ${entry.version ?? "?"} \u2192 ${entry.latest ?? "?"}`,
2177
+ source: "composer-outdated"
2178
+ });
2179
+ } catch {
2180
+ }
2181
+ }
2182
+ return { diagnostics, vulnerabilities: [], outdated };
2183
+ }
2184
+ function parseDotnetPackage(text) {
2185
+ const diagnostics = [];
2186
+ const vulnerabilities = [];
2187
+ const outdated = [];
2188
+ let root;
2189
+ try {
2190
+ root = JSON.parse(text);
2191
+ } catch {
2192
+ return { diagnostics, vulnerabilities, outdated };
2193
+ }
2194
+ const projects = root.projects;
2195
+ if (!Array.isArray(projects)) return { diagnostics, vulnerabilities, outdated };
2196
+ for (const project of projects) {
2197
+ const packages = Array.isArray(project.packages) ? project.packages : [];
2198
+ for (const pkg of packages) {
2199
+ const name = typeof pkg.name === "string" ? pkg.name : "unknown";
2200
+ const requested = typeof pkg.requestedVersion === "string" ? pkg.requestedVersion : void 0;
2201
+ const resolved = typeof pkg.resolvedVersion === "string" ? pkg.resolvedVersion : void 0;
2202
+ const vulnerabilitiesRaw = Array.isArray(pkg.vulnerabilities) ? pkg.vulnerabilities : [];
2203
+ for (const vuln of vulnerabilitiesRaw) {
2204
+ const advisory = typeof vuln.advisoryUrl === "string" ? vuln.advisoryUrl : "dotnet-vulnerable";
2205
+ vulnerabilities.push({
2206
+ package: name,
2207
+ advisory,
2208
+ severity: mapSeverity(typeof vuln.severity === "string" ? vuln.severity : void 0)
2209
+ });
2210
+ }
2211
+ if (vulnerabilitiesRaw.length > 0) {
2212
+ diagnostics.push({
2213
+ severity: "warning",
2214
+ code: "dotnet-vulnerable",
2215
+ message: `${name} has ${vulnerabilitiesRaw.length} known vulnerability entry/entries.`,
2216
+ source: "dotnet-package"
2217
+ });
2218
+ }
2219
+ if (requested && resolved && requested.startsWith(">") && requested.split(">")[1].split(".").slice(0, 2).join(".") !== resolved.split(".").slice(0, 2).join(".")) {
2220
+ outdated.push({ name, requested, resolved });
2221
+ diagnostics.push({
2222
+ severity: "info",
2223
+ code: "outdated",
2224
+ message: `${name}: ${requested} \u2192 ${resolved}`,
2225
+ source: "dotnet-package"
2226
+ });
2227
+ }
2228
+ }
2229
+ }
2230
+ return { diagnostics, vulnerabilities, outdated };
2231
+ }
2232
+ function mapSeverity(value) {
2233
+ switch (value?.toLowerCase()) {
2234
+ case "critical":
2235
+ case "high":
2236
+ return "high";
2237
+ case "medium":
2238
+ case "moderate":
2239
+ return "moderate";
2240
+ case "low":
2241
+ return "low";
2242
+ case "unknown":
2243
+ return "unknown";
2244
+ default:
2245
+ return "unknown";
2246
+ }
2247
+ }
2248
+ function mapOutdatedKind(value) {
2249
+ switch (value?.toLowerCase()) {
2250
+ case "devdependencies":
2251
+ case "development":
2252
+ return "development";
2253
+ case "optionaldependencies":
2254
+ case "optional":
2255
+ return "optional";
2256
+ default:
2257
+ return "runtime";
2258
+ }
2259
+ }
2260
+ function parseLinePattern(text, regex, root, source, details) {
2261
+ const diagnostics = [];
2262
+ for (const match of text.matchAll(regex)) {
2263
+ const parsed = details(match, match.at(-1)?.trim() ?? "Diagnostic");
2264
+ diagnostics.push({
2265
+ severity: parsed.severity,
2266
+ message: parsed.message,
2267
+ file: normalizeDiagnosticPath(match[1], root),
2268
+ range: { start: { line: toPositiveInt(match[2]), column: toPositiveInt(match[3]) } },
2269
+ source
2270
+ });
2271
+ }
2272
+ return diagnostics;
2273
+ }
2274
+ function normalizeDiagnosticPath(value, root) {
2275
+ const clean = value.trim().replace(/^['"]|['"]$/g, "");
2276
+ return path4.resolve(root, clean);
2277
+ }
2278
+ function normalizeSeverity(value) {
2279
+ if (value === "warning" || value === "warn") return "warning";
2280
+ if (value === "info" || value === "note" || value === "help") return "info";
2281
+ if (value === "hint") return "hint";
2282
+ return "error";
2283
+ }
2284
+ function toPositiveInt(value) {
2285
+ return Math.max(1, Number.parseInt(value ?? "1", 10) || 1);
2286
+ }
2287
+ function summarize(diagnostics) {
2288
+ return {
2289
+ errors: diagnostics.filter((item) => item.severity === "error").length,
2290
+ warnings: diagnostics.filter((item) => item.severity === "warning").length,
2291
+ infos: diagnostics.filter((item) => item.severity === "info" || item.severity === "hint").length
2292
+ };
2293
+ }
2294
+ function emptySummary() {
2295
+ return { errors: 0, warnings: 0, infos: 0 };
2296
+ }
2297
+ function dedupeDiagnostics(items) {
2298
+ const seen = /* @__PURE__ */ new Set();
2299
+ return items.filter((item) => {
2300
+ const key = [
2301
+ item.source,
2302
+ item.code ?? "",
2303
+ item.file ?? "",
2304
+ item.range?.start.line ?? 0,
2305
+ item.range?.start.column ?? 0,
2306
+ item.message
2307
+ ].join("\0");
2308
+ if (seen.has(key)) return false;
2309
+ seen.add(key);
2310
+ return true;
2311
+ });
2312
+ }
2313
+ function compareDiagnostics(a, b) {
2314
+ 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);
2315
+ }
2316
+ function severityRank(value) {
2317
+ return value === "error" ? 0 : value === "warning" ? 1 : value === "info" ? 2 : 3;
2318
+ }
2319
+ var MAX_DIAGNOSTICS;
2320
+ var init_diagnostics = __esm({
2321
+ "src/languages/diagnostics.ts"() {
2322
+ "use strict";
2323
+ MAX_DIAGNOSTICS = 200;
2324
+ }
2325
+ });
2326
+
2327
+ // src/_output-spool.ts
2328
+ import { createWriteStream, mkdirSync } from "node:fs";
2329
+ import * as fsp2 from "node:fs/promises";
2330
+ import * as path5 from "node:path";
2331
+ import { wstackGlobalRoot as wstackGlobalRoot2 } from "@wrongstack/core";
2332
+ function toolOutputDir() {
2333
+ return path5.join(wstackGlobalRoot2(), "tool-output");
2334
+ }
2335
+ function sweepOldSpoolFiles(dir) {
2336
+ if (sweepStarted) return;
2337
+ sweepStarted = true;
2338
+ void (async () => {
2339
+ try {
2340
+ const now = Date.now();
2341
+ for (const name of await fsp2.readdir(dir)) {
2342
+ if (!name.endsWith(".log")) continue;
2343
+ const p = path5.join(dir, name);
2344
+ try {
2345
+ const st = await fsp2.stat(p);
2346
+ if (now - st.mtimeMs > SPOOL_RETENTION_MS) await fsp2.unlink(p);
2347
+ } catch {
2348
+ }
2349
+ }
2350
+ } catch {
2351
+ }
2352
+ })();
2353
+ }
2354
+ function spoolNote(info) {
2355
+ const dropped = info.droppedBytes > 0 ? `, ~${info.droppedBytes} bytes dropped under backpressure` : "";
2356
+ return `
2357
+ [output truncated \u2014 full ${info.bytes} bytes at ${info.path}${dropped}; read/grep that file selectively instead of re-running with more output]`;
2358
+ }
2359
+ function createOutputSpool(opts) {
2360
+ const threshold = opts.thresholdBytes ?? 32768;
2361
+ const safeTool = opts.tool.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
2362
+ let head = "";
2363
+ let headBytes = 0;
2364
+ let totalBytes = 0;
2365
+ let droppedBytes = 0;
2366
+ let stream = null;
2367
+ let filePath = null;
2368
+ let failed = false;
2369
+ let finalized = false;
2370
+ const open = () => {
2371
+ if (stream || failed) return;
2372
+ try {
2373
+ const dir = toolOutputDir();
2374
+ mkdirSync(dir, { recursive: true });
2375
+ sweepOldSpoolFiles(dir);
2376
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
2377
+ const rand = Math.random().toString(36).slice(2, 6);
2378
+ filePath = path5.join(dir, `${stamp}-${safeTool}-${rand}.log`);
2379
+ stream = createWriteStream(filePath, { flags: "w", encoding: "utf8" });
2380
+ stream.on("error", () => {
2381
+ failed = true;
2382
+ stream = null;
2383
+ filePath = null;
2384
+ });
2385
+ stream.write(head);
2386
+ } catch {
2387
+ failed = true;
2388
+ stream = null;
2389
+ filePath = null;
2390
+ }
2391
+ };
2392
+ return {
2393
+ write(text) {
2394
+ if (finalized || !text) return;
2395
+ totalBytes += Buffer.byteLength(text, "utf8");
2396
+ if (!stream && !failed) {
2397
+ if (headBytes + text.length <= threshold) {
2398
+ head += text;
2399
+ headBytes += text.length;
2400
+ return;
2401
+ }
2402
+ head += text;
2403
+ open();
2404
+ head = "";
2405
+ return;
2406
+ }
2407
+ if (stream) {
2408
+ if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
2409
+ droppedBytes += Buffer.byteLength(text, "utf8");
2410
+ return;
2411
+ }
2412
+ stream.write(text);
2413
+ }
2414
+ },
2415
+ finalize() {
2416
+ if (finalized) {
2417
+ return filePath ? { path: filePath, bytes: totalBytes, droppedBytes } : null;
2418
+ }
2419
+ finalized = true;
2420
+ head = "";
2421
+ if (!stream || !filePath) return null;
2422
+ try {
2423
+ stream.end();
2424
+ } catch {
2425
+ }
2426
+ return { path: filePath, bytes: totalBytes, droppedBytes };
2427
+ }
2428
+ };
2429
+ }
2430
+ var SPOOL_RETENTION_MS, SPOOL_WRITE_HWM_BYTES, sweepStarted;
2431
+ var init_output_spool = __esm({
2432
+ "src/_output-spool.ts"() {
2433
+ "use strict";
2434
+ SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
2435
+ SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
2436
+ sweepStarted = false;
2437
+ }
2438
+ });
2439
+
2440
+ // src/circuit-breaker.ts
2441
+ var DEFAULT_MAX_CONSECUTIVE_FAILURES, DEFAULT_SLOW_CALL_THRESHOLD_MS, DEFAULT_MAX_SLOW_CALLS, DEFAULT_WINDOW_MS, DEFAULT_MAX_CALLS_PER_WINDOW, DEFAULT_COOLDOWN_MS, CircuitBreaker;
2442
+ var init_circuit_breaker = __esm({
2443
+ "src/circuit-breaker.ts"() {
2444
+ "use strict";
2445
+ DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
2446
+ DEFAULT_SLOW_CALL_THRESHOLD_MS = 18e4;
2447
+ DEFAULT_MAX_SLOW_CALLS = 3;
2448
+ DEFAULT_WINDOW_MS = 6e4;
2449
+ DEFAULT_MAX_CALLS_PER_WINDOW = 30;
2450
+ DEFAULT_COOLDOWN_MS = 3e4;
2451
+ CircuitBreaker = class {
2452
+ maxConsecutiveFailures;
2453
+ slowCallThresholdMs;
2454
+ maxSlowCalls;
2455
+ windowMs;
2456
+ maxCallsPerWindow;
2457
+ cooldownMs;
2458
+ state = "closed";
2459
+ consecutiveFailures = 0;
2460
+ window = [];
2461
+ lastFailureAt = null;
2462
+ lastSlowAt = null;
2463
+ /** Timestamp when the breaker was opened (for cooldown calculation). */
2464
+ openedAt = null;
2465
+ /**
2466
+ * Master enable flag. When false the breaker is bypassed: `beforeCall`
2467
+ * always returns true and `afterCall` records nothing. The class itself
2468
+ * defaults to enabled (so the standalone unit tests exercise tripping); the
2469
+ * ProcessRegistry flips this off until the user opts in via `/settings`.
2470
+ */
2471
+ enabled = true;
2472
+ /**
2473
+ * Fired (best-effort) when the breaker transitions into the `open` state.
2474
+ * The registry uses this to arm its auto kill/reset countdown.
2475
+ */
2476
+ onTrip;
2477
+ /**
2478
+ * Fired (best-effort) when the breaker returns to `closed` after having been
2479
+ * open/half-open. The registry uses this to cancel a pending kill/reset.
2480
+ */
2481
+ onReset;
2482
+ constructor(config = {}) {
2483
+ this.maxConsecutiveFailures = config.maxConsecutiveFailures ?? DEFAULT_MAX_CONSECUTIVE_FAILURES;
2484
+ this.slowCallThresholdMs = config.slowCallThresholdMs ?? DEFAULT_SLOW_CALL_THRESHOLD_MS;
2485
+ this.maxSlowCalls = config.maxSlowCalls ?? DEFAULT_MAX_SLOW_CALLS;
2486
+ this.windowMs = config.windowMs ?? DEFAULT_WINDOW_MS;
2487
+ this.maxCallsPerWindow = config.maxCallsPerWindow ?? DEFAULT_MAX_CALLS_PER_WINDOW;
2488
+ this.cooldownMs = config.cooldownMs ?? DEFAULT_COOLDOWN_MS;
2489
+ }
2490
+ /** Toggle the master enable. Disabling resets to a clean `closed` state. */
2491
+ setEnabled(enabled) {
2492
+ if (this.enabled === enabled) return;
2493
+ this.enabled = enabled;
2494
+ if (!enabled) this._reset();
2495
+ }
2496
+ get isEnabled() {
2497
+ return this.enabled;
2498
+ }
2499
+ /**
2500
+ * Returns true if the circuit allows a new call to proceed.
2501
+ * When false, callers should abort the tool call and return a
2502
+ * circuit-breaker error instead of spawning a process.
2503
+ */
2504
+ get canProceed() {
2505
+ if (!this.enabled) return true;
2506
+ this._checkStateTransition();
2507
+ return this.state !== "open";
2508
+ }
2509
+ /**
2510
+ * Snapshot of the current breaker state for observability (`/kill`).
2511
+ */
2512
+ snapshot() {
2513
+ this._checkStateTransition();
2514
+ const now = Date.now();
2515
+ let cooldownRemaining = null;
2516
+ if (this.openedAt !== null && this.state === "open") {
2517
+ const elapsed = now - this.openedAt;
2518
+ cooldownRemaining = Math.max(0, this.cooldownMs - elapsed);
2519
+ }
2520
+ return {
2521
+ state: this.state,
2522
+ consecutiveFailures: this.consecutiveFailures,
2523
+ slowCallsInWindow: this.window.filter((c) => c.slow).length,
2524
+ callsInWindow: this.window.length,
2525
+ windowMs: this.windowMs,
2526
+ cooldownRemainingMs: cooldownRemaining,
2527
+ lastFailureAt: this.lastFailureAt,
2528
+ lastSlowAt: this.lastSlowAt
2529
+ };
2530
+ }
2531
+ /**
2532
+ * Call this BEFORE spawning a bash/exec process.
2533
+ * Returns true if the call is allowed; false if the breaker is open.
2534
+ * When false, callers MUST NOT spawn a process.
2535
+ *
2536
+ * @param bypass - If true, skip the circuit breaker check entirely.
2537
+ * Use for background/fire-and-forget processes that should
2538
+ * not affect breaker state.
2539
+ */
2540
+ beforeCall(bypass = false) {
2541
+ if (bypass || !this.enabled) return true;
2542
+ this._checkStateTransition();
2543
+ if (this.state === "open") return false;
2544
+ return true;
2545
+ }
2546
+ /**
2547
+ * Call this AFTER a bash/exec process finishes (success or failure).
2548
+ * `durationMs` is the wall-clock time the process ran.
2549
+ * `failed` is true when the process returned a non-zero exit code or
2550
+ * threw an exception before spawning.
2551
+ *
2552
+ * @param bypass - If true, do not update breaker state.
2553
+ * Use for background/fire-and-forget processes.
2554
+ */
2555
+ afterCall(durationMs, failed, bypass = false) {
2556
+ if (bypass || !this.enabled) return;
2557
+ const now = Date.now();
2558
+ if (this.state === "half-open") {
2559
+ if (failed) {
2560
+ this._trip();
2561
+ return;
2562
+ }
2563
+ this._reset();
2564
+ return;
2565
+ }
2566
+ this._pruneWindow(now);
2567
+ const slow = durationMs >= this.slowCallThresholdMs;
2568
+ this.window.push({ at: now, failed, slow });
2569
+ if (failed) {
2570
+ this.consecutiveFailures++;
2571
+ this.lastFailureAt = now;
2572
+ if (this.consecutiveFailures >= this.maxConsecutiveFailures) {
2573
+ this._trip();
2574
+ }
2575
+ return;
2576
+ }
2577
+ this.consecutiveFailures = 0;
2578
+ if (slow) {
2579
+ this.lastSlowAt = now;
2580
+ const slowCount = this.window.filter((c) => c.slow).length;
2581
+ if (slowCount >= this.maxSlowCalls) {
2582
+ this._trip();
2583
+ }
2584
+ }
2585
+ const callCount = this.window.length;
2586
+ if (callCount >= this.maxCallsPerWindow) {
2587
+ this._trip();
2588
+ }
2589
+ }
2590
+ /** Force the breaker open. Used by /kill force and Ctrl+C. */
2591
+ forceOpen() {
2592
+ this._trip();
2593
+ }
2594
+ /** Force a reset to closed. Used by tests and /kill reset. */
2595
+ forceReset() {
2596
+ this._reset();
2597
+ }
2598
+ _trip() {
2599
+ if (this.state === "open") return;
2600
+ this.state = "open";
2601
+ this.openedAt = Date.now();
2602
+ this.window = [];
2603
+ try {
2604
+ this.onTrip?.();
2605
+ } catch {
2606
+ }
2607
+ }
2608
+ _reset() {
2609
+ const wasRecovering = this.state !== "closed";
2610
+ this.state = "closed";
2611
+ this.consecutiveFailures = 0;
2612
+ this.window = [];
2613
+ this.openedAt = null;
2614
+ if (wasRecovering) {
2615
+ try {
2616
+ this.onReset?.();
2617
+ } catch {
2618
+ }
2619
+ }
2620
+ }
2621
+ /** Transition from open → half-open when cooldown elapses. */
2622
+ _checkStateTransition() {
2623
+ if (this.state !== "open" || this.openedAt === null) return;
2624
+ const elapsed = Date.now() - this.openedAt;
2625
+ if (elapsed >= this.cooldownMs) {
2626
+ this.state = "half-open";
2627
+ this.openedAt = null;
2628
+ }
2629
+ }
2630
+ _pruneWindow(now) {
2631
+ const cutoff = now - this.windowMs;
2632
+ this.window = this.window.filter((c) => c.at >= cutoff);
2633
+ }
2634
+ };
2635
+ }
2636
+ });
2637
+
2638
+ // src/_redact-command.ts
2639
+ import { expectDefined } from "@wrongstack/core";
2640
+ function redactCommand(cmd) {
2641
+ let result = cmd;
2642
+ for (const pattern of SENSITIVE_FLAG_PATTERNS) {
2643
+ result = result.replace(pattern, (match) => {
2644
+ const eq = match.indexOf("=");
2645
+ const sp = match.search(/\s/);
2646
+ const delim = eq !== -1 ? "=" : sp !== -1 ? match[sp] : null;
2647
+ if (delim !== null) {
2648
+ const flag = match.slice(0, match.indexOf(expectDefined(delim)) + 1);
2649
+ return `${flag}[REDACTED]`;
2650
+ }
2651
+ const flagEnd = match.match(/^--?[a-zA-Z][a-zA-Z0-9_-]*/)?.[0] ?? match;
2652
+ return `${flagEnd}=**redacted**`;
2653
+ });
2654
+ }
2655
+ return result;
2656
+ }
2657
+ var SENSITIVE_FLAG_PATTERNS;
2658
+ var init_redact_command = __esm({
2659
+ "src/_redact-command.ts"() {
2660
+ "use strict";
2661
+ SENSITIVE_FLAG_PATTERNS = [
2662
+ // --flag=value or --flag "value" (value captured up to next space or comma)
2663
+ /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
2664
+ // -f "value" style short flags
2665
+ /(?<!\w)-t(?:\s+|\s*=\s*)[^\s,]+/,
2666
+ /(?<!\w)-(?:p|password)(?:\s+|\s*=\s*)[^\s,]+/gi,
2667
+ // env var–style secrets: TOKEN=x, API_KEY=y, etc.
2668
+ /(?:TOKEN|API_KEY|API_SECRET|AUTH_TOKEN|GITHUB_TOKEN|GH_TOKEN|BEARER|JWT|OAUTH|CREDENTIAL|SECRET|PRIVATE_KEY|PASSWORD|PASSWD)\s*[=:]\s*[^\s,]+/gi,
2669
+ // Generic high-entropy look: base64 strings >32 chars or hex strings >32 digits — but only
2670
+ // when preceded by a flag name (e.g. --github-token=EyJ...).
2671
+ /--\w*(?:token|key|secret|password|passwd|auth|credential)\w*[=\s,][A-Za-z0-9+/=]{32,}/
2672
+ ];
2673
+ }
2674
+ });
2675
+
2676
+ // src/process-registry.ts
2677
+ import { spawn } from "node:child_process";
2678
+ import * as os from "node:os";
2679
+ function killWin32Tree(pid, opts = {}) {
2680
+ try {
2681
+ const child = spawn("taskkill", ["/pid", String(pid), "/T", "/F"], {
2682
+ stdio: "ignore",
2683
+ windowsHide: true
2684
+ });
2685
+ let settled = false;
2686
+ let timeout;
2687
+ const settle = () => {
2688
+ if (settled) return;
2689
+ settled = true;
2690
+ if (timeout) clearTimeout(timeout);
2691
+ try {
2692
+ opts.onSettled?.();
2693
+ } catch {
2694
+ }
2695
+ };
2696
+ child.on("error", settle);
2697
+ child.on("close", settle);
2698
+ timeout = setTimeout(() => {
2699
+ try {
2700
+ child.kill();
2701
+ } catch {
2702
+ }
2703
+ settle();
2704
+ }, Math.max(1, opts.timeoutMs ?? WIN32_TASKKILL_TIMEOUT_MS));
2705
+ timeout.unref?.();
2706
+ child.unref();
2707
+ return true;
2708
+ } catch {
2709
+ return false;
2710
+ }
2711
+ }
2712
+ function getProcessRegistry() {
2713
+ if (!_registry) {
2714
+ _registry = new ProcessRegistryImpl();
2715
+ }
2716
+ return _registry;
2717
+ }
2718
+ var DEFAULT_GRACE_MS, WIN32_TASKKILL_TIMEOUT_MS, ProcessRegistryImpl, _registry;
2719
+ var init_process_registry = __esm({
2720
+ "src/process-registry.ts"() {
2721
+ "use strict";
2722
+ init_circuit_breaker();
2723
+ init_redact_command();
2724
+ DEFAULT_GRACE_MS = 2e3;
2725
+ WIN32_TASKKILL_TIMEOUT_MS = 5e3;
2726
+ ProcessRegistryImpl = class {
2727
+ processes = /* @__PURE__ */ new Map();
2728
+ breaker;
2729
+ /**
2730
+ * Auto kill/reset config. When the breaker trips and `autoKillResetMs > 0`,
2731
+ * a countdown is armed; on expiry all tracked processes are killed and the
2732
+ * breaker is reset to closed (forced recovery). Zero means manual recovery
2733
+ * only (`/kill reset`).
2734
+ */
2735
+ autoKillResetMs = 0;
2736
+ autoKillTimer = null;
2737
+ autoKillArmedAt = null;
2738
+ breakerCountdownListeners = [];
2739
+ constructor(breakerConfig) {
2740
+ this.breaker = new CircuitBreaker(breakerConfig);
2741
+ this.breaker.onTrip = () => this._armAutoKillReset();
2742
+ this.breaker.onReset = () => this._cancelAutoKillReset();
2743
+ this.breaker.setEnabled(false);
2744
+ }
2745
+ register(info) {
2746
+ this.processes.set(info.pid, { ...info, killed: false, protected: info.protected ?? false });
2747
+ }
2748
+ _isSafeSignalPid(pid) {
2749
+ return Number.isInteger(pid) && pid > 1 && pid !== process.pid && pid !== process.ppid;
2750
+ }
2751
+ _canSignalProcessGroup(p) {
2752
+ return os.platform() !== "win32" && p.processGroupLeader === true && this._isSafeSignalPid(p.pid) && typeof p.child.pid === "number" && p.child.pid === p.pid;
2753
+ }
2754
+ _killChildDirect(p, signal) {
2755
+ try {
2756
+ p.child.kill(signal);
2757
+ } catch {
2758
+ }
2759
+ }
2760
+ _killPosix(p, signal) {
2761
+ if (this._canSignalProcessGroup(p)) {
2762
+ try {
2763
+ process.kill(-p.pid, signal);
2764
+ return;
2765
+ } catch {
2766
+ }
2767
+ }
2768
+ this._killChildDirect(p, signal);
2769
+ }
2770
+ /** Unregister a process by PID. Called on 'close' / 'exit' events. */
2771
+ unregister(pid) {
2772
+ this.processes.delete(pid);
2773
+ }
2774
+ /** Get a single process by PID. */
2775
+ get(pid) {
2776
+ this._pruneStale(pid);
2777
+ return this.processes.get(pid);
2778
+ }
2779
+ /** Get all tracked processes. */
2780
+ list() {
2781
+ return Array.from(this.processes.values());
2782
+ }
2783
+ /** Get processes filtered by name (e.g. 'bash', 'exec'). */
2784
+ byName(name) {
2785
+ return this.list().filter((p) => p.name === name);
2786
+ }
2787
+ /** Get processes filtered by session. */
2788
+ bySession(sessionId) {
2789
+ return this.list().filter((p) => p.sessionId === sessionId);
2790
+ }
2791
+ /** Count of active (non-killed) processes. */
2792
+ get activeCount() {
2793
+ let n = 0;
2794
+ for (const p of this.processes.values()) {
2795
+ if (!p.killed) n++;
2796
+ }
2797
+ return n;
2798
+ }
2799
+ /**
2800
+ * Combined stats for observability — used by /ps and the TUI status bar.
2801
+ */
2802
+ stats() {
2803
+ return {
2804
+ activeCount: this.activeCount,
2805
+ totalCount: this.processes.size,
2806
+ breaker: this.breaker.snapshot()
2807
+ };
2808
+ }
2809
+ /**
2810
+ * Returns true if the circuit allows a new bash/exec call to proceed.
2811
+ * When false, callers MUST NOT spawn a process.
2812
+ */
2813
+ get canProceed() {
2814
+ return this.breaker.canProceed;
2815
+ }
2816
+ /**
2817
+ * Called before spawning a process. Returns true if allowed; false if
2818
+ * the circuit breaker is open.
2819
+ *
2820
+ * @param bypass - If true, skip circuit breaker check (for background processes).
2821
+ */
2822
+ beforeCall(bypass = false) {
2823
+ return this.breaker.beforeCall(bypass);
2824
+ }
2825
+ /**
2826
+ * Called after a process finishes. `durationMs` is wall-clock time;
2827
+ * `failed` is true for non-zero exit codes.
2828
+ *
2829
+ * @param bypass - If true, do not update circuit breaker state (for background processes).
2830
+ */
2831
+ afterCall(durationMs, failed, bypass = false) {
2832
+ this.breaker.afterCall(durationMs, failed, bypass);
2833
+ }
2834
+ /** Force-open the circuit breaker (Ctrl+C, /kill force). */
2835
+ forceBreakerOpen() {
2836
+ this.breaker.forceOpen();
2837
+ }
2838
+ /** Force-reset the circuit breaker to closed (/kill reset). */
2839
+ forceBreakerReset() {
2840
+ this.breaker.forceReset();
2841
+ }
2842
+ /**
2843
+ * Configure circuit-breaker protection at runtime. Called from `/settings`
2844
+ * (instant, all modes) and on TUI mount (applies persisted config).
2845
+ *
2846
+ * - `enabled` toggles whether the breaker gates `bash`/`exec`.
2847
+ * - `autoKillResetMs` arms the auto kill/reset countdown when the breaker
2848
+ * trips (0 = manual recovery only).
2849
+ *
2850
+ * Re-applies cleanly on every call: cancels a pending countdown when the
2851
+ * timeout is cleared or protection disabled, and re-arms if the breaker is
2852
+ * currently open under the new settings.
2853
+ */
2854
+ setBreakerConfig(cfg) {
2855
+ if (cfg.enabled !== void 0) this.breaker.setEnabled(cfg.enabled);
2856
+ if (cfg.autoKillResetMs !== void 0) this.autoKillResetMs = Math.max(0, cfg.autoKillResetMs);
2857
+ if (this.autoKillResetMs <= 0) {
2858
+ this._cancelAutoKillReset();
2859
+ return;
2860
+ }
2861
+ if (this.breaker.isEnabled && this.breaker.snapshot().state === "open") {
2862
+ this._armAutoKillReset();
2863
+ }
2864
+ }
2865
+ /**
2866
+ * Live countdown to the next auto kill/reset, or null when nothing is armed.
2867
+ * The TUI polls this on a 1s tick while armed so the statusline decrements.
2868
+ */
2869
+ getBreakerCountdown() {
2870
+ if (this.autoKillArmedAt === null || this.autoKillResetMs <= 0) return null;
2871
+ const elapsed = Date.now() - this.autoKillArmedAt;
2872
+ return { remainingMs: Math.max(0, this.autoKillResetMs - elapsed), totalMs: this.autoKillResetMs };
2873
+ }
2874
+ /**
2875
+ * Subscribe to countdown arm/cancel events. Returns an unsubscribe function.
2876
+ * Use {@link getBreakerCountdown} for the live ticking value between events.
2877
+ */
2878
+ onBreakerCountdownChange(listener) {
2879
+ this.breakerCountdownListeners.push(listener);
2880
+ return () => {
2881
+ this.breakerCountdownListeners = this.breakerCountdownListeners.filter((l) => l !== listener);
2882
+ };
2883
+ }
2884
+ _emitBreakerCountdown() {
2885
+ const snap = this.getBreakerCountdown();
2886
+ for (const l of this.breakerCountdownListeners) {
2887
+ try {
2888
+ l(snap);
2889
+ } catch {
2890
+ }
2891
+ }
2892
+ }
2893
+ /**
2894
+ * Arm the auto kill/reset countdown. Idempotent: re-arming resets the window
2895
+ * (a fresh trip after a failed half-open probe restarts the clock). No-op
2896
+ * when protection is off or no timeout is configured.
2897
+ */
2898
+ _armAutoKillReset() {
2899
+ if (this.autoKillResetMs <= 0 || !this.breaker.isEnabled) return;
2900
+ this._clearAutoKillTimer();
2901
+ this.autoKillArmedAt = Date.now();
2902
+ this.autoKillTimer = setTimeout(() => {
2903
+ this.autoKillTimer = null;
2904
+ this.autoKillArmedAt = null;
2905
+ this.killAll({ force: false });
2906
+ this.breaker.forceReset();
2907
+ this._emitBreakerCountdown();
2908
+ }, this.autoKillResetMs);
2909
+ this.autoKillTimer.unref?.();
2910
+ this._emitBreakerCountdown();
2911
+ }
2912
+ _cancelAutoKillReset() {
2913
+ const wasArmed = this.autoKillArmedAt !== null;
2914
+ this._clearAutoKillTimer();
2915
+ if (wasArmed) {
2916
+ this.autoKillArmedAt = null;
2917
+ this._emitBreakerCountdown();
2918
+ }
2919
+ }
2920
+ _clearAutoKillTimer() {
2921
+ if (this.autoKillTimer !== null) {
2922
+ clearTimeout(this.autoKillTimer);
2923
+ this.autoKillTimer = null;
2924
+ }
2925
+ }
2926
+ /** Kill a single process by PID.
2927
+ *
2928
+ * On POSIX: sends SIGTERM to the *process group* (-pid) so that
2929
+ * runaway grandchild processes (`sleep 9999 & disown`) are also killed.
2930
+ * After `graceMs` a SIGKILL is sent if the process hasn't exited.
2931
+ *
2932
+ * On Windows: `child.kill()` maps to TerminateProcess — process groups
2933
+ * are not meaningfully supported. A second `force=true` call sends
2934
+ * SIGKILL (which maps to TerminateProcess again — the distinction is
2935
+ * in the exit code, not the signal).
2936
+ *
2937
+ * Returns true if the process was found and kill was attempted.
2938
+ */
2939
+ kill(pid, opts = {}) {
2940
+ this._pruneStale(pid);
2941
+ const p = this.processes.get(pid);
2942
+ if (!p) return false;
2943
+ if (p.killed) return true;
2944
+ if (p.protected) return false;
2945
+ const { force = false, graceMs = DEFAULT_GRACE_MS } = opts;
2946
+ const isWin2 = os.platform() === "win32";
2947
+ if (isWin2) {
2948
+ const liveRealChild = p.child.exitCode === null && typeof p.child.pid === "number";
2949
+ const directFallback = () => {
2950
+ if (p.child.exitCode === null) {
2951
+ try {
2952
+ p.child.kill("SIGKILL");
2953
+ } catch {
2954
+ }
2955
+ }
2956
+ };
2957
+ if (liveRealChild && killWin32Tree(pid, {
2958
+ timeoutMs: Math.max(graceMs, WIN32_TASKKILL_TIMEOUT_MS),
2959
+ onSettled: directFallback
2960
+ })) {
2961
+ } else {
2962
+ try {
2963
+ p.child.kill(force ? "SIGKILL" : "SIGTERM");
2964
+ } catch {
2965
+ }
2966
+ }
2967
+ p.killed = true;
2968
+ return true;
2969
+ }
2970
+ try {
2971
+ if (force) {
2972
+ this._killPosix(p, "SIGKILL");
2973
+ } else {
2974
+ this._killPosix(p, "SIGTERM");
2975
+ const timer = setTimeout(() => {
2976
+ if (this.processes.has(pid) && !p.child.killed) {
2977
+ this._killPosix(p, "SIGKILL");
2978
+ }
2979
+ }, graceMs);
2980
+ timer.unref?.();
2981
+ }
2982
+ } catch {
2983
+ }
2984
+ p.killed = true;
2985
+ return true;
2986
+ }
2987
+ /**
2988
+ * Kill all tracked processes.
2989
+ * Returns the PIDs that were kill()ed.
2990
+ */
2991
+ killAll(opts = {}) {
2992
+ const pids = Array.from(this.processes.keys());
2993
+ const killed = [];
2994
+ for (const pid of pids) {
2995
+ const p = this.processes.get(pid);
2996
+ if (p && !p.protected && this.kill(pid, opts)) killed.push(pid);
2997
+ }
2998
+ return killed;
2999
+ }
3000
+ /**
3001
+ * Kill all processes for a specific session.
3002
+ * Returns the PIDs that were kill()ed.
3003
+ */
3004
+ killSession(sessionId, opts = {}) {
3005
+ const pids = this.bySession(sessionId).map((p) => p.pid);
3006
+ const killed = [];
3007
+ for (const pid of pids) {
3008
+ if (this.kill(pid, opts)) killed.push(pid);
3009
+ }
3010
+ return killed;
3011
+ }
3012
+ /**
3013
+ * Check whether a tracked process entry is stale — the child has exited
3014
+ * (exitCode !== null) AND it's been in the registry long enough that the
3015
+ * OS may have reused the PID for a new, unrelated process.
3016
+ *
3017
+ * P3 #24 (before-release.md): on POSIX, PIDs are reused after process
3018
+ * exit. If a tracked process exits but its 'close' event hasn't fired yet
3019
+ * (or was missed), the registry still holds the entry. A new process
3020
+ * gets the same PID, and PID-based lookups (get, kill, shouldBlockKill)
3021
+ * may incorrectly protect or target the wrong process.
3022
+ *
3023
+ * The 60s threshold is conservative — the OS typically waits much longer
3024
+ * before reusing a PID, but we want to clean up before that becomes a risk.
3025
+ */
3026
+ _isStaleEntry(entry) {
3027
+ return entry.child.exitCode !== null && Date.now() - entry.startedAt > 6e4;
3028
+ }
3029
+ /**
3030
+ * Remove a stale entry for a specific PID before any PID-based lookup.
3031
+ * This prevents PID reuse from causing the registry to act on a dead
3032
+ * process that has been replaced by a new one with the same PID.
3033
+ */
3034
+ _pruneStale(pid) {
3035
+ const entry = this.processes.get(pid);
3036
+ if (entry && this._isStaleEntry(entry)) {
3037
+ this.processes.delete(pid);
3038
+ }
3039
+ }
3040
+ };
3041
+ }
3042
+ });
3043
+
3044
+ // src/_spawn-stream.ts
3045
+ import { spawn as spawn2 } from "node:child_process";
3046
+ import { buildChildEnv } from "@wrongstack/core";
3047
+ async function* spawnStream(opts) {
3048
+ const max = opts.maxBytes ?? 2e5;
3049
+ const flushAt = opts.flushBytes ?? 4 * 1024;
3050
+ const maxQueue = opts.maxQueueSize ?? 500;
3051
+ let stdout = "";
3052
+ let stderr = "";
3053
+ let pending = "";
3054
+ let error;
3055
+ const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
3056
+ const resolved = resolveWin32Command(opts.cmd);
3057
+ const needsShell = isWin && (resolved.endsWith(".cmd") || resolved.endsWith(".bat"));
3058
+ const shim = needsShell ? buildWin32CmdShimInvocation(resolved, opts.args) : null;
3059
+ const cmd = shim?.command ?? resolved;
3060
+ const args = shim?.args ?? opts.args;
3061
+ const child = spawn2(cmd, args, {
3062
+ cwd: opts.cwd,
3063
+ env: buildChildEnv(),
3064
+ stdio: ["ignore", "pipe", "pipe"],
3065
+ windowsHide: true,
3066
+ ...isWin ? {} : { signal: opts.signal },
3067
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
3068
+ });
3069
+ const registry = getProcessRegistry();
3070
+ const pid = child.pid;
3071
+ if (typeof pid === "number") {
3072
+ registry.register({
3073
+ pid,
3074
+ name: opts.cmd,
3075
+ command: redactCommand(`${opts.cmd} ${opts.args.join(" ")}`),
3076
+ startedAt: Date.now(),
3077
+ child
3078
+ });
3079
+ }
3080
+ const queue = [];
3081
+ let waiter;
3082
+ let paused = false;
3083
+ const wake = () => {
3084
+ if (waiter) {
3085
+ const w = waiter;
3086
+ waiter = void 0;
3087
+ w();
3088
+ }
3089
+ };
3090
+ const resume = () => {
3091
+ if (paused && queue.length < maxQueue) {
3092
+ paused = false;
3093
+ child.stdout?.resume();
3094
+ child.stderr?.resume();
3095
+ }
3096
+ };
3097
+ const onOut = (c) => {
3098
+ const s = c.toString();
3099
+ if (stdout.length < max) stdout += s;
3100
+ spool.write(s);
3101
+ queue.push({ kind: "out", data: s });
3102
+ wake();
3103
+ if (!paused && queue.length >= maxQueue) {
3104
+ paused = true;
3105
+ child.stdout?.pause();
3106
+ child.stderr?.pause();
3107
+ }
3108
+ };
3109
+ const onErr = (c) => {
3110
+ const s = c.toString();
3111
+ if (stderr.length < max) stderr += s;
3112
+ spool.write(s);
3113
+ queue.push({ kind: "err", data: s });
3114
+ wake();
3115
+ if (!paused && queue.length >= maxQueue) {
3116
+ paused = true;
3117
+ child.stdout?.pause();
3118
+ child.stderr?.pause();
3119
+ }
3120
+ };
3121
+ child.stdout?.on("data", onOut);
3122
+ child.stderr?.on("data", onErr);
3123
+ child.on("error", (e) => {
3124
+ error = e.message;
3125
+ queue.push({ kind: "error", data: e.message });
3126
+ wake();
3127
+ });
3128
+ child.on("close", (code) => {
3129
+ if (typeof pid === "number") registry.unregister(pid);
3130
+ queue.push({ kind: "close", data: "", code: code ?? 0 });
3131
+ wake();
3132
+ });
3133
+ const onAbort = () => {
3134
+ if (typeof pid === "number") {
3135
+ registry.kill(pid, { force: true });
3136
+ } else {
3137
+ try {
3138
+ child.kill("SIGKILL");
3139
+ } catch {
3140
+ }
3141
+ }
3142
+ queue.push({ kind: "close", data: "", code: 124 });
3143
+ wake();
3144
+ };
3145
+ if (isWin) {
3146
+ if (opts.signal.aborted) onAbort();
3147
+ else opts.signal.addEventListener("abort", onAbort, { once: true });
3148
+ }
3149
+ let exitCode = 0;
3150
+ let spawnFailed = false;
3151
+ try {
3152
+ for (; ; ) {
3153
+ while (queue.length === 0) {
3154
+ await new Promise((resolve6) => {
3155
+ waiter = resolve6;
3156
+ });
3157
+ }
3158
+ const chunk = queue.shift();
3159
+ resume();
3160
+ if (chunk.kind === "close") {
3161
+ if (!spawnFailed) exitCode = chunk.code ?? 0;
3162
+ break;
3163
+ }
3164
+ if (chunk.kind === "error") {
3165
+ spawnFailed = true;
3166
+ exitCode = 1;
3167
+ continue;
3168
+ }
3169
+ pending += chunk.data;
3170
+ if (pending.length >= flushAt) {
3171
+ yield { type: "partial_output", text: pending };
3172
+ pending = "";
3173
+ }
3174
+ }
3175
+ if (pending.length > 0) {
3176
+ yield { type: "partial_output", text: pending };
3177
+ }
3178
+ const spooled = spool.finalize();
3179
+ return {
3180
+ // The marker rides on stdout's tail so every consumer's head+tail
3181
+ // normalization keeps it without per-tool changes.
3182
+ stdout: spooled ? stdout + spoolNote(spooled) : stdout,
3183
+ stderr,
3184
+ exitCode,
3185
+ truncated: stdout.length >= max || stderr.length >= max,
3186
+ error,
3187
+ spoolPath: spooled?.path,
3188
+ spoolBytes: spooled?.bytes
3189
+ };
3190
+ } finally {
3191
+ spool.finalize();
3192
+ if (isWin) opts.signal.removeEventListener("abort", onAbort);
3193
+ child.stdout?.off("data", onOut);
3194
+ child.stderr?.off("data", onErr);
3195
+ child.stdout?.destroy();
3196
+ child.stderr?.destroy();
3197
+ if (child.exitCode === null && !child.killed) {
3198
+ if (typeof pid === "number") {
3199
+ registry.kill(pid, { force: true });
3200
+ } else {
3201
+ try {
3202
+ child.kill("SIGKILL");
3203
+ } catch {
3204
+ }
3205
+ }
3206
+ }
3207
+ }
3208
+ }
3209
+ var isWin;
3210
+ var init_spawn_stream = __esm({
3211
+ "src/_spawn-stream.ts"() {
3212
+ "use strict";
3213
+ init_output_spool();
3214
+ init_process_registry();
3215
+ init_win32_resolve();
3216
+ isWin = process.platform === "win32";
3217
+ }
3218
+ });
6
3219
 
7
- // src/outdated.ts
8
- async function detectPackageManager(cwd) {
9
- const { stat } = await import('node:fs/promises');
3220
+ // src/languages/plan.ts
3221
+ import * as fs3 from "node:fs/promises";
3222
+ import * as path6 from "node:path";
3223
+ async function planLanguageOperation(options) {
3224
+ validateOperationOptions(options.operation, options.operationOptions?.packages);
3225
+ const detection = await detectLanguageWorkspaces(options);
3226
+ const candidates = detection.workspaces.filter(
3227
+ (workspace) => workspace.capabilities.includes(options.operation)
3228
+ );
3229
+ const selected = selectWorkspace(candidates, options);
3230
+ if (selected.status !== "selected") return selected.result;
3231
+ const profile = (options.profiles ?? languageProfileRegistry.list()).find(
3232
+ (item) => item.id === selected.workspace.language
3233
+ );
3234
+ if (!profile) {
3235
+ return {
3236
+ status: "not_found",
3237
+ reason: `Profile ${selected.workspace.language} is unavailable.`,
3238
+ candidates
3239
+ };
3240
+ }
3241
+ if ((profile.id === "typescript" || profile.id === "javascript") && options.operation !== "syntax" && selected.workspace.packageManager === void 0 && new Set(
3242
+ selected.workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.value.toLowerCase())
3243
+ ).size > 1) {
3244
+ return {
3245
+ status: "unavailable",
3246
+ workspace: selected.workspace,
3247
+ unavailable: {
3248
+ status: "unavailable",
3249
+ profileId: profile.id,
3250
+ workspaceId: selected.workspace.id,
3251
+ operation: options.operation,
3252
+ reason: "Conflicting Node lockfiles make the package manager ambiguous."
3253
+ }
3254
+ };
3255
+ }
3256
+ const resolver = profile.operations[options.operation];
3257
+ if (!resolver) {
3258
+ return {
3259
+ status: "unavailable",
3260
+ workspace: selected.workspace,
3261
+ unavailable: {
3262
+ status: "unavailable",
3263
+ profileId: profile.id,
3264
+ workspaceId: selected.workspace.id,
3265
+ operation: options.operation,
3266
+ reason: `${profile.displayName} does not define ${options.operation}.`
3267
+ }
3268
+ };
3269
+ }
3270
+ const canonicalCwd = options.cwd ? path6.isAbsolute(options.cwd) ? options.cwd : path6.resolve(detection.projectRoot, options.cwd) : detection.projectRoot;
3271
+ const target = options.target ? await canonicalTarget(canonicalCwd, options.target, detection.projectRoot) : void 0;
3272
+ const ctx = {
3273
+ projectRoot: detection.projectRoot,
3274
+ workspace: selected.workspace,
3275
+ ...target ? { target } : {},
3276
+ mode: options.mode ?? "standard",
3277
+ options: options.operationOptions ?? {},
3278
+ platform: process.platform,
3279
+ pathExists: async (candidate) => {
3280
+ try {
3281
+ await fs3.access(path6.resolve(detection.projectRoot, candidate));
3282
+ return true;
3283
+ } catch {
3284
+ return false;
3285
+ }
3286
+ }
3287
+ };
3288
+ const result = await resolver(ctx);
3289
+ if ("status" in result) {
3290
+ return { status: "unavailable", workspace: selected.workspace, unavailable: result };
3291
+ }
3292
+ const errors = validateCommandPlan(result, profile, detection.projectRoot);
3293
+ if (errors.length > 0) {
3294
+ return {
3295
+ status: "unavailable",
3296
+ workspace: selected.workspace,
3297
+ unavailable: {
3298
+ status: "unavailable",
3299
+ profileId: profile.id,
3300
+ workspaceId: selected.workspace.id,
3301
+ operation: options.operation,
3302
+ reason: `Generated plan failed validation: ${errors.join("; ")}`
3303
+ }
3304
+ };
3305
+ }
3306
+ return { status: "planned", workspace: selected.workspace, plan: Object.freeze(result) };
3307
+ }
3308
+ function validateCommandPlan(plan, profile, projectRoot) {
3309
+ const errors = [];
3310
+ if (plan.profileId !== profile.id) errors.push("profile id does not match");
3311
+ if (!isInside2(plan.cwd, projectRoot)) errors.push("cwd is outside project root");
3312
+ if (plan.args.length > MAX_ARGUMENTS) errors.push(`argument count exceeds ${MAX_ARGUMENTS}`);
3313
+ if (plan.args.some((arg) => arg.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(arg))) {
3314
+ errors.push("arguments contain an invalid or oversized value");
3315
+ }
3316
+ if (!Number.isFinite(plan.timeoutMs) || plan.timeoutMs < 1 || plan.timeoutMs > 6e5) {
3317
+ errors.push("timeout is outside 1..600000ms");
3318
+ }
3319
+ if (!Number.isFinite(plan.outputLimitBytes) || plan.outputLimitBytes < 1 || plan.outputLimitBytes > 1e6) {
3320
+ errors.push("output limit is outside 1..1000000 bytes");
3321
+ }
3322
+ if (plan.kind === "internal") {
3323
+ if (plan.command !== null || plan.args.length !== 0)
3324
+ errors.push("internal plans cannot declare a command");
3325
+ } else {
3326
+ if (!plan.command || !profile.executables.includes(plan.command)) {
3327
+ errors.push(`executable "${plan.command ?? ""}" is not allowlisted by the profile`);
3328
+ }
3329
+ }
3330
+ if (Object.keys(plan.env).length > 0)
3331
+ errors.push("Phase 1 plans cannot override environment variables");
3332
+ return errors;
3333
+ }
3334
+ function selectWorkspace(candidates, options) {
3335
+ if (candidates.length === 0) {
3336
+ return {
3337
+ status: "result",
3338
+ result: {
3339
+ status: "not_found",
3340
+ reason: `No workspace supports ${options.operation}.`,
3341
+ candidates: []
3342
+ }
3343
+ };
3344
+ }
3345
+ if (options.workspace) {
3346
+ const requested = path6.isAbsolute(options.workspace) ? path6.resolve(options.workspace) : path6.resolve(options.projectRoot, options.workspace);
3347
+ const matches = candidates.filter(
3348
+ (item) => item.id === options.workspace || path6.resolve(item.root) === requested
3349
+ );
3350
+ if (matches.length === 1) return { status: "selected", workspace: matches[0] };
3351
+ return {
3352
+ status: "result",
3353
+ result: {
3354
+ status: "not_found",
3355
+ reason: `Requested workspace "${options.workspace}" was not detected.`,
3356
+ candidates: [...candidates]
3357
+ }
3358
+ };
3359
+ }
3360
+ if (options.target) {
3361
+ const base = options.cwd ? path6.isAbsolute(options.cwd) ? path6.resolve(options.cwd) : path6.resolve(options.projectRoot, options.cwd) : path6.resolve(options.projectRoot);
3362
+ const target = path6.isAbsolute(options.target) ? path6.resolve(options.target) : path6.resolve(base, options.target);
3363
+ const targeted = candidates.filter((item) => item.evidence.some((evidence) => evidence.kind === "target")).sort(
3364
+ (a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
3365
+ );
3366
+ if (targeted[0]) return { status: "selected", workspace: targeted[0] };
3367
+ const containing = candidates.filter((item) => isInside2(target, item.root)).sort(
3368
+ (a, b) => workspaceDepth(b.root, options.projectRoot) - workspaceDepth(a.root, options.projectRoot) || compareCandidates(a, b)
3369
+ );
3370
+ if (containing[0]) return { status: "selected", workspace: containing[0] };
3371
+ }
3372
+ const sorted = [...candidates].sort(compareCandidates);
3373
+ const first = sorted[0];
3374
+ const firstDepth = workspaceDepth(first.root, options.projectRoot);
3375
+ const tied = sorted.filter(
3376
+ (item) => item.confidence === first.confidence && workspaceDepth(item.root, options.projectRoot) === firstDepth
3377
+ );
3378
+ if (tied.length > 1) {
3379
+ return {
3380
+ status: "result",
3381
+ result: {
3382
+ status: "ambiguous",
3383
+ reason: "Multiple workspaces have equal confidence; provide target, language, or workspace.",
3384
+ candidates: tied
3385
+ }
3386
+ };
3387
+ }
3388
+ return { status: "selected", workspace: first };
3389
+ }
3390
+ function compareCandidates(a, b) {
3391
+ return b.confidence - a.confidence || a.language.localeCompare(b.language) || a.root.localeCompare(b.root);
3392
+ }
3393
+ function workspaceDepth(workspaceRoot, projectRoot) {
3394
+ const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3395
+ return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3396
+ }
3397
+ function validateOperationOptions(operation, packages) {
3398
+ if (!operation.startsWith("package-") || !packages) return;
3399
+ for (const value of packages) {
3400
+ if (!value || value.length > 214 || value.startsWith("-") || /[\r\n\0;&|`$<>\\]/.test(value)) {
3401
+ throw new Error(`Invalid package identifier "${value}"`);
3402
+ }
3403
+ if (value.startsWith(".") || value.startsWith("/") || /^[A-Za-z]:/.test(value) || value.includes("://")) {
3404
+ throw new Error(`Package paths and URLs are not supported: "${value}"`);
3405
+ }
3406
+ if (!PACKAGE_NAME_RE.test(value) && !COMPOSER_PACKAGE_RE.test(value) && !GO_MODULE_RE.test(value)) {
3407
+ throw new Error(`Invalid package identifier "${value}"`);
3408
+ }
3409
+ }
3410
+ }
3411
+ async function canonicalTarget(cwd, target, projectRoot) {
3412
+ const resolved = path6.isAbsolute(target) ? path6.resolve(target) : path6.resolve(cwd, target);
3413
+ let real;
10
3414
  try {
11
- await stat(`${cwd}/pnpm-lock.yaml`);
12
- return "pnpm";
13
- } catch {
3415
+ real = await fs3.realpath(resolved);
3416
+ } catch (error) {
3417
+ if (error.code !== "ENOENT") throw error;
3418
+ const parent = await fs3.realpath(path6.dirname(resolved));
3419
+ real = path6.join(parent, path6.basename(resolved));
3420
+ }
3421
+ if (!isInside2(real, projectRoot)) throw new Error(`target is outside project root: ${target}`);
3422
+ return real;
3423
+ }
3424
+ function isInside2(candidate, root) {
3425
+ const relative5 = path6.relative(path6.resolve(root), path6.resolve(candidate));
3426
+ return relative5 === "" || !relative5.startsWith("..") && !path6.isAbsolute(relative5);
3427
+ }
3428
+ var MAX_ARGUMENTS, MAX_ARGUMENT_LENGTH, PACKAGE_NAME_RE, COMPOSER_PACKAGE_RE, GO_MODULE_RE;
3429
+ var init_plan = __esm({
3430
+ "src/languages/plan.ts"() {
3431
+ "use strict";
3432
+ init_detect();
3433
+ init_registry();
3434
+ MAX_ARGUMENTS = 128;
3435
+ MAX_ARGUMENT_LENGTH = 4096;
3436
+ PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
3437
+ COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
3438
+ GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3439
+ }
3440
+ });
3441
+
3442
+ // src/languages/execute.ts
3443
+ import * as fs4 from "node:fs/promises";
3444
+ import * as path7 from "node:path";
3445
+ async function* executeLanguagePlan(options) {
3446
+ const { plan, workspace } = options;
3447
+ if (options.signal.aborted) {
3448
+ return terminalResult(options, "cancelled", options.signal.reason);
3449
+ }
3450
+ const profile = languageProfileRegistry.get(plan.profileId);
3451
+ if (!profile) return unavailableResult(options, `Unknown language profile: ${plan.profileId}`);
3452
+ if (workspace.id !== plan.workspaceId || workspace.language !== plan.profileId) {
3453
+ return unavailableResult(options, "Plan and workspace identity do not match.");
3454
+ }
3455
+ const validation = validateCommandPlan(plan, profile, options.projectRoot);
3456
+ if (validation.length > 0) {
3457
+ return unavailableResult(options, `Plan failed execution validation: ${validation.join("; ")}`);
3458
+ }
3459
+ const containmentError = await validatePlanRealpaths(plan, options.projectRoot);
3460
+ if (containmentError) return unavailableResult(options, containmentError);
3461
+ if (plan.operation.startsWith("package-") || plan.network) {
3462
+ return terminalNonPackageResult(
3463
+ options,
3464
+ "Package and network plans require the separate language_package tool (Phase 3)."
3465
+ );
14
3466
  }
3467
+ const startedAt = Date.now();
3468
+ yield {
3469
+ type: "log",
3470
+ text: plan.kind === "internal" ? `Running ${plan.parser}\u2026` : `${plan.command} ${plan.args.join(" ")}`,
3471
+ data: { language: plan.profileId, operation: plan.operation, workspace: workspace.root }
3472
+ };
3473
+ if (plan.kind === "internal") {
3474
+ try {
3475
+ return await executeInternal(options, startedAt);
3476
+ } catch (error) {
3477
+ return unavailableResult(options, error instanceof Error ? error.message : String(error));
3478
+ }
3479
+ }
3480
+ const timeoutController = new AbortController();
3481
+ const timer = setTimeout(
3482
+ () => timeoutController.abort(new Error("language plan timed out")),
3483
+ plan.timeoutMs
3484
+ );
3485
+ timer.unref?.();
3486
+ const signal = AbortSignal.any([options.signal, timeoutController.signal]);
3487
+ let spawned;
15
3488
  try {
16
- await stat(`${cwd}/yarn.lock`);
17
- return "yarn";
18
- } catch {
3489
+ const stream = spawnStream({
3490
+ cmd: plan.command,
3491
+ args: [...plan.args],
3492
+ cwd: plan.cwd,
3493
+ signal,
3494
+ maxBytes: plan.outputLimitBytes
3495
+ });
3496
+ for (; ; ) {
3497
+ const next = await stream.next();
3498
+ if (next.done) {
3499
+ spawned = next.value;
3500
+ break;
3501
+ }
3502
+ yield next.value;
3503
+ }
3504
+ } catch (error) {
3505
+ const timedOut2 = timeoutController.signal.aborted && !options.signal.aborted;
3506
+ const cancelled2 = options.signal.aborted;
3507
+ return {
3508
+ status: timedOut2 ? "timed_out" : cancelled2 ? "cancelled" : "failed",
3509
+ language: plan.profileId,
3510
+ workspace,
3511
+ plan,
3512
+ exitCode: null,
3513
+ durationMs: Date.now() - startedAt,
3514
+ diagnostics: Object.freeze([]),
3515
+ omittedDiagnostics: 0,
3516
+ summary: emptySummary2(),
3517
+ output: "",
3518
+ truncated: false,
3519
+ error: error instanceof Error ? error.message : String(error)
3520
+ };
3521
+ } finally {
3522
+ clearTimeout(timer);
19
3523
  }
20
- return "npm";
3524
+ const parsed = parseLanguageDiagnostics(
3525
+ plan.parser,
3526
+ spawned.stdout,
3527
+ spawned.stderr,
3528
+ workspace.root
3529
+ );
3530
+ const timedOut = timeoutController.signal.aborted && !options.signal.aborted;
3531
+ const cancelled = options.signal.aborted;
3532
+ const spawnUnavailable = Boolean(
3533
+ spawned.error && /ENOENT|not found|cannot find/i.test(spawned.error)
3534
+ );
3535
+ const status = timedOut ? "timed_out" : cancelled ? "cancelled" : spawnUnavailable ? "unavailable" : spawned.exitCode === 0 ? "passed" : "failed";
3536
+ const raw = [spawned.stdout, spawned.stderr, spawned.error].filter(Boolean).join("\n");
3537
+ return {
3538
+ status,
3539
+ language: plan.profileId,
3540
+ workspace,
3541
+ plan,
3542
+ exitCode: spawned.exitCode,
3543
+ durationMs: Date.now() - startedAt,
3544
+ diagnostics: parsed.diagnostics,
3545
+ omittedDiagnostics: parsed.omitted,
3546
+ summary: parsed.summary,
3547
+ output: normalizeCommandOutput(raw, { maxBytes: plan.outputLimitBytes }),
3548
+ truncated: spawned.truncated,
3549
+ ...spawned.spoolPath ? { spoolPath: spawned.spoolPath } : {},
3550
+ ...spawned.error ? { error: spawned.error } : {}
3551
+ };
21
3552
  }
22
- function resolvePath(input, ctx) {
23
- return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);
3553
+ async function executeInternal(options, startedAt) {
3554
+ const target = options.plan.evidence.find((item) => item.kind === "target")?.path;
3555
+ if (!target) return unavailableResult(options, "Internal syntax plan has no target evidence.");
3556
+ const safeTarget = await assertContainedFile(target, options.projectRoot);
3557
+ const stat5 = await fs4.stat(safeTarget);
3558
+ if (stat5.size > MAX_INTERNAL_SOURCE_BYTES) {
3559
+ return unavailableResult(
3560
+ options,
3561
+ `Internal syntax target exceeds ${MAX_INTERNAL_SOURCE_BYTES} bytes.`
3562
+ );
3563
+ }
3564
+ const source = await fs4.readFile(safeTarget, "utf8");
3565
+ const parsed = await diagnosticsForInternalSyntax(options.plan.profileId, safeTarget, source);
3566
+ return {
3567
+ status: parsed.summary.errors > 0 ? "failed" : "passed",
3568
+ language: options.plan.profileId,
3569
+ workspace: options.workspace,
3570
+ plan: options.plan,
3571
+ exitCode: parsed.summary.errors > 0 ? 1 : 0,
3572
+ durationMs: Date.now() - startedAt,
3573
+ diagnostics: parsed.diagnostics,
3574
+ omittedDiagnostics: parsed.omitted,
3575
+ summary: parsed.summary,
3576
+ output: parsed.summary.errors > 0 ? `${parsed.summary.errors} syntax error(s) found.` : "Syntax check passed.",
3577
+ truncated: false
3578
+ };
24
3579
  }
25
- function allowedRoots(ctx) {
26
- return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];
3580
+ async function validatePlanRealpaths(plan, projectRoot) {
3581
+ const realRoot = await fs4.realpath(projectRoot);
3582
+ let realCwd;
3583
+ try {
3584
+ realCwd = await fs4.realpath(plan.cwd);
3585
+ } catch (error) {
3586
+ return `Plan cwd is unavailable: ${error instanceof Error ? error.message : String(error)}`;
3587
+ }
3588
+ if (!isRealInside(realCwd, realRoot)) return "Plan cwd resolves outside project root.";
3589
+ for (const argument of plan.args) {
3590
+ if (!path7.isAbsolute(argument)) continue;
3591
+ try {
3592
+ const realArgument = await fs4.realpath(argument);
3593
+ if (!isRealInside(realArgument, realRoot)) {
3594
+ return `Plan argument resolves outside project root: ${argument}`;
3595
+ }
3596
+ } catch (error) {
3597
+ if (error.code !== "ENOENT") {
3598
+ return `Plan argument could not be resolved safely: ${argument}`;
3599
+ }
3600
+ }
3601
+ }
3602
+ return void 0;
27
3603
  }
28
- function isInsideAny(target, roots) {
29
- return roots.some((root) => {
30
- const rel = path.relative(root, target);
31
- return rel === "" || !rel.startsWith("..") && !path.isAbsolute(rel);
32
- });
3604
+ function isRealInside(candidate, root) {
3605
+ const relative5 = path7.relative(root, candidate);
3606
+ return relative5 === "" || !relative5.startsWith("..") && !path7.isAbsolute(relative5);
33
3607
  }
34
- function ensureInsideRoot(absPath, ctx) {
35
- const target = path.resolve(absPath);
36
- if (ctx.allowOutsideProjectRoot) return target;
37
- if (isInsideAny(target, allowedRoots(ctx))) return target;
38
- throw new Error(`Path "${absPath}" is outside project root "${path.resolve(ctx.projectRoot)}"`);
3608
+ async function assertContainedFile(candidate, projectRoot) {
3609
+ const realRoot = await fs4.realpath(projectRoot);
3610
+ const realTarget = await fs4.realpath(candidate);
3611
+ const relative5 = path7.relative(realRoot, realTarget);
3612
+ if (relative5.startsWith("..") || path7.isAbsolute(relative5)) {
3613
+ throw new Error(`Internal syntax target resolves outside project root: ${candidate}`);
3614
+ }
3615
+ return realTarget;
39
3616
  }
40
- function safeResolve(input, ctx) {
41
- return ensureInsideRoot(resolvePath(input, ctx), ctx);
3617
+ function terminalResult(options, status, reason) {
3618
+ const message = reason instanceof Error ? reason.message : reason ? String(reason) : status;
3619
+ return {
3620
+ status,
3621
+ language: options.plan.profileId,
3622
+ workspace: options.workspace,
3623
+ plan: options.plan,
3624
+ exitCode: null,
3625
+ durationMs: 0,
3626
+ diagnostics: Object.freeze([]),
3627
+ omittedDiagnostics: 0,
3628
+ summary: emptySummary2(),
3629
+ output: "",
3630
+ truncated: false,
3631
+ error: message
3632
+ };
42
3633
  }
43
- function resolveWin32Command(cmd) {
44
- if (process.platform !== "win32") return cmd;
45
- if (cmd.includes("/") || cmd.includes("\\") || path.extname(cmd.replace(/\//g, "\\"))) {
46
- return cmd;
3634
+ function unavailableResult(options, reason) {
3635
+ return {
3636
+ status: "unavailable",
3637
+ language: options.plan.profileId,
3638
+ workspace: options.workspace,
3639
+ plan: options.plan,
3640
+ exitCode: null,
3641
+ durationMs: 0,
3642
+ diagnostics: Object.freeze([]),
3643
+ omittedDiagnostics: 0,
3644
+ summary: emptySummary2(),
3645
+ output: reason,
3646
+ truncated: false,
3647
+ error: reason
3648
+ };
3649
+ }
3650
+ function emptySummary2() {
3651
+ return { errors: 0, warnings: 0, infos: 0 };
3652
+ }
3653
+ function terminalNonPackageResult(options, reason) {
3654
+ return {
3655
+ status: "unavailable",
3656
+ language: options.plan.profileId,
3657
+ workspace: options.workspace,
3658
+ plan: options.plan,
3659
+ exitCode: null,
3660
+ durationMs: 0,
3661
+ diagnostics: Object.freeze([]),
3662
+ omittedDiagnostics: 0,
3663
+ summary: emptySummary2(),
3664
+ output: reason,
3665
+ truncated: false,
3666
+ error: reason
3667
+ };
3668
+ }
3669
+ async function* executePackagePlan(options) {
3670
+ const { plan, workspace } = options;
3671
+ const startedAt = Date.now();
3672
+ if (options.signal.aborted) {
3673
+ yield { type: "log", text: "Run cancelled before execution." };
3674
+ return terminalPackageResult(options, "cancelled", startedAt);
47
3675
  }
48
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
49
- const pathDirs = (process.env["PATH"] ?? "").split(path.delimiter);
50
- for (const dir of pathDirs) {
51
- const base = path.join(dir, cmd);
52
- for (const ext of pathext) {
53
- const full = `${base}${ext}`;
54
- try {
55
- fs.accessSync(full, fs.constants.X_OK);
56
- return full;
57
- } catch {
3676
+ const profile = languageProfileRegistry.get(plan.profileId);
3677
+ if (!profile) {
3678
+ yield { type: "warning", text: `Unknown language profile: ${plan.profileId}` };
3679
+ return terminalPackageResult(options, "unavailable", startedAt, "Unknown language profile.");
3680
+ }
3681
+ if (workspace.id !== plan.workspaceId || workspace.language !== plan.profileId) {
3682
+ return terminalPackageResult(
3683
+ options,
3684
+ "unavailable",
3685
+ startedAt,
3686
+ "Plan and workspace identity do not match."
3687
+ );
3688
+ }
3689
+ const validation = validateCommandPlan(plan, profile, options.projectRoot);
3690
+ if (validation.length > 0) {
3691
+ return terminalPackageResult(
3692
+ options,
3693
+ "unavailable",
3694
+ startedAt,
3695
+ `Plan failed execution validation: ${validation.join("; ")}`
3696
+ );
3697
+ }
3698
+ const containmentError = await validatePlanRealpaths(plan, options.projectRoot);
3699
+ if (containmentError) {
3700
+ return terminalPackageResult(options, "unavailable", startedAt, containmentError);
3701
+ }
3702
+ const manifestsBefore = await snapshotPaths(workspace.manifests);
3703
+ const lockfilePaths = collectLockfilePaths(workspace);
3704
+ const lockfilesBefore = await snapshotPaths(lockfilePaths);
3705
+ const manifestSizesBefore = await snapshotSizes(workspace.manifests);
3706
+ const lockfileSizesBefore = await snapshotSizes(lockfilePaths);
3707
+ yield {
3708
+ type: "log",
3709
+ text: plan.kind === "process" ? `${plan.command} ${plan.args.join(" ")}` : `${plan.parser}: ${plan.operation}`,
3710
+ data: {
3711
+ language: plan.profileId,
3712
+ operation: plan.operation,
3713
+ workspace: workspace.root,
3714
+ packages: [...options.packages]
3715
+ }
3716
+ };
3717
+ const timeoutController = new AbortController();
3718
+ const timer = setTimeout(
3719
+ () => timeoutController.abort(new Error("language package plan timed out")),
3720
+ plan.timeoutMs
3721
+ );
3722
+ timer.unref?.();
3723
+ const signal = AbortSignal.any([options.signal, timeoutController.signal]);
3724
+ let spawned;
3725
+ let run;
3726
+ let status = "unavailable";
3727
+ let error;
3728
+ try {
3729
+ if (plan.kind !== "process") {
3730
+ return terminalPackageResult(
3731
+ options,
3732
+ "unavailable",
3733
+ startedAt,
3734
+ "Package operations require an executable plan; internal plans cannot mutate the manifest."
3735
+ );
3736
+ }
3737
+ const stream = spawnStream({
3738
+ cmd: plan.command,
3739
+ args: [...plan.args],
3740
+ cwd: plan.cwd,
3741
+ signal,
3742
+ maxBytes: plan.outputLimitBytes
3743
+ });
3744
+ for (; ; ) {
3745
+ const next = await stream.next();
3746
+ if (next.done) {
3747
+ spawned = next.value;
3748
+ break;
58
3749
  }
3750
+ yield next.value;
59
3751
  }
3752
+ } catch (error_) {
3753
+ error = error_ instanceof Error ? error_.message : String(error_);
3754
+ } finally {
3755
+ clearTimeout(timer);
60
3756
  }
61
- return cmd;
3757
+ if (spawned) {
3758
+ const parsed = parseLanguageDiagnostics(
3759
+ plan.parser,
3760
+ spawned.stdout,
3761
+ spawned.stderr,
3762
+ workspace.root
3763
+ );
3764
+ const timedOut = timeoutController.signal.aborted && !options.signal.aborted;
3765
+ const cancelled = options.signal.aborted;
3766
+ const spawnUnavailable = Boolean(
3767
+ spawned.error && /ENOENT|not found|cannot find/i.test(spawned.error)
3768
+ );
3769
+ status = timedOut ? "timed_out" : cancelled ? "cancelled" : spawnUnavailable ? "unavailable" : spawned.exitCode === 0 ? "passed" : "failed";
3770
+ error ??= spawned.error;
3771
+ const raw = [spawned.stdout, spawned.stderr, spawned.error].filter(Boolean).join("\n");
3772
+ run = {
3773
+ status,
3774
+ language: plan.profileId,
3775
+ workspace,
3776
+ plan,
3777
+ exitCode: spawned.exitCode,
3778
+ durationMs: Date.now() - startedAt,
3779
+ diagnostics: parsed.diagnostics,
3780
+ omittedDiagnostics: parsed.omitted,
3781
+ summary: parsed.summary,
3782
+ output: normalizeCommandOutput(raw, { maxBytes: plan.outputLimitBytes }),
3783
+ truncated: spawned.truncated,
3784
+ ...spawned.spoolPath ? { spoolPath: spawned.spoolPath } : {},
3785
+ ...spawned.error ? { error: spawned.error } : {}
3786
+ };
3787
+ } else {
3788
+ status = options.signal.aborted ? "cancelled" : timeoutController.signal.aborted ? "timed_out" : "failed";
3789
+ }
3790
+ if (status !== "passed") {
3791
+ return terminalPackageResult(
3792
+ options,
3793
+ status,
3794
+ startedAt,
3795
+ error,
3796
+ run,
3797
+ manifestsBefore,
3798
+ lockfilesBefore
3799
+ );
3800
+ }
3801
+ const manifestsAfter = await snapshotPaths(workspace.manifests);
3802
+ const lockfilesAfter = await snapshotPaths(lockfilePaths);
3803
+ const manifestSizesAfter = await snapshotSizes(workspace.manifests);
3804
+ const lockfileSizesAfter = await snapshotSizes(lockfilePaths);
3805
+ const manifestsChanged = await changedPaths(
3806
+ manifestsBefore,
3807
+ manifestsAfter,
3808
+ manifestSizesBefore,
3809
+ manifestSizesAfter
3810
+ );
3811
+ const lockfilesChanged = await changedPaths(
3812
+ lockfilesBefore,
3813
+ lockfilesAfter,
3814
+ lockfileSizesBefore,
3815
+ lockfileSizesAfter
3816
+ );
3817
+ const reports = parsePackageReports(plan.parser, spawned?.stdout ?? "", spawned?.stderr ?? "");
3818
+ const mutations = packageMutationsFromInputs(options.packages, reports.outdated);
3819
+ return {
3820
+ workspace,
3821
+ language: plan.profileId,
3822
+ operation: plan.operation,
3823
+ status,
3824
+ ...run ? { run } : {},
3825
+ durationMs: Date.now() - startedAt,
3826
+ manifestsBefore,
3827
+ lockfilesBefore,
3828
+ manifestsAfter,
3829
+ lockfilesAfter,
3830
+ mutations,
3831
+ vulnerabilities: reports.vulnerabilities,
3832
+ outdated: reports.outdated,
3833
+ manifestsChanged: Object.freeze(manifestsChanged),
3834
+ lockfilesChanged: Object.freeze(lockfilesChanged)
3835
+ };
62
3836
  }
63
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
64
- function assertSafeWin32ShellArgs(args) {
65
- for (const arg of args) {
66
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
67
- throw new Error(
68
- 'win32 cmd shim spawn: argument contains a shell metacharacter (one of & | < > ", or a newline) that could enable command injection through the .cmd/.bat wrapper - refusing to run. Offending argument: ' + JSON.stringify(arg)
69
- );
3837
+ function packageMutationsFromInputs(inputs, outdated) {
3838
+ const outdatedNames = new Set(outdated.map((entry) => entry.name));
3839
+ return inputs.filter((name) => !outdatedNames.has(name)).map((name) => ({ name, requested: name, kind: "runtime" }));
3840
+ }
3841
+ async function snapshotPaths(paths) {
3842
+ const existing = [];
3843
+ for (const candidate of paths) {
3844
+ try {
3845
+ const stat5 = await fs4.stat(candidate);
3846
+ if (!stat5.isFile()) continue;
3847
+ existing.push(candidate);
3848
+ } catch {
70
3849
  }
71
3850
  }
3851
+ return Object.freeze(existing);
72
3852
  }
73
- function buildWin32CmdShimInvocation(command, args = []) {
74
- assertSafeWin32ShellArgs([command, ...args]);
75
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
3853
+ async function changedPaths(before, after, beforeSizes, afterSizes) {
3854
+ const beforeSet = new Set(before);
3855
+ const afterSet = new Set(after);
3856
+ const changed = /* @__PURE__ */ new Set();
3857
+ for (const path9 of after) {
3858
+ if (!beforeSet.has(path9)) changed.add(path9);
3859
+ }
3860
+ for (const path9 of before) {
3861
+ if (!afterSet.has(path9)) changed.add(path9);
3862
+ }
3863
+ if (beforeSizes && afterSizes) {
3864
+ for (const path9 of after) {
3865
+ if (beforeSizes.get(path9) !== afterSizes.get(path9)) changed.add(path9);
3866
+ }
3867
+ }
3868
+ return [...changed].sort();
3869
+ }
3870
+ async function snapshotSizes(paths) {
3871
+ const sizes = /* @__PURE__ */ new Map();
3872
+ for (const candidate of paths) {
3873
+ try {
3874
+ const stat5 = await fs4.stat(candidate);
3875
+ if (stat5.isFile()) sizes.set(candidate, stat5.size);
3876
+ } catch {
3877
+ }
3878
+ }
3879
+ return sizes;
3880
+ }
3881
+ function collectLockfilePaths(workspace) {
3882
+ const explicit = workspace.evidence.filter((evidence) => evidence.kind === "lockfile").map((evidence) => evidence.path);
3883
+ const detected = LOCKFILE_NAMES_BY_PROFILE.get(workspace.language)?.map(
3884
+ (name) => path7.join(workspace.root, name)
3885
+ ) ?? [];
3886
+ const merged = /* @__PURE__ */ new Set();
3887
+ for (const candidate of [...explicit, ...detected]) merged.add(candidate);
3888
+ return Object.freeze([...merged]);
3889
+ }
3890
+ function terminalPackageResult(options, status, startedAt, error, run, manifestsBefore = [], lockfilesBefore = []) {
76
3891
  return {
77
- command: process.env["COMSPEC"] ?? "cmd.exe",
78
- args: ["/d", "/c", line],
79
- windowsVerbatimArguments: true
3892
+ workspace: options.workspace,
3893
+ language: options.plan.profileId,
3894
+ operation: options.plan.operation,
3895
+ status,
3896
+ ...run ? { run } : {},
3897
+ durationMs: Date.now() - startedAt,
3898
+ manifestsBefore,
3899
+ lockfilesBefore,
3900
+ manifestsAfter: manifestsBefore,
3901
+ lockfilesAfter: lockfilesBefore,
3902
+ manifestsChanged: Object.freeze([]),
3903
+ lockfilesChanged: Object.freeze([]),
3904
+ mutations: Object.freeze([]),
3905
+ vulnerabilities: Object.freeze([]),
3906
+ outdated: Object.freeze([]),
3907
+ ...error ? { error } : {}
80
3908
  };
81
3909
  }
82
- function quoteWin32CmdArg(arg) {
83
- return `"${arg}"`;
3910
+ var MAX_INTERNAL_SOURCE_BYTES, LOCKFILE_NAMES_BY_PROFILE;
3911
+ var init_execute = __esm({
3912
+ "src/languages/execute.ts"() {
3913
+ "use strict";
3914
+ init_spawn_stream();
3915
+ init_util();
3916
+ init_diagnostics();
3917
+ init_plan();
3918
+ init_registry();
3919
+ MAX_INTERNAL_SOURCE_BYTES = 15e5;
3920
+ LOCKFILE_NAMES_BY_PROFILE = /* @__PURE__ */ new Map([
3921
+ ["typescript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
3922
+ ["javascript", ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"]],
3923
+ ["go", ["go.sum"]],
3924
+ ["rust", ["Cargo.lock"]],
3925
+ ["php", ["composer.lock"]],
3926
+ ["csharp", ["packages.lock.json"]]
3927
+ ]);
3928
+ }
3929
+ });
3930
+
3931
+ // src/languages/execute-tool.ts
3932
+ function operationsFor(input) {
3933
+ switch (input.action) {
3934
+ case "check":
3935
+ return input.check === "syntax" ? ["syntax"] : input.check === "semantic" ? ["semantic"] : ["syntax", "semantic"];
3936
+ case "lint":
3937
+ return ["lint"];
3938
+ case "format":
3939
+ return [input.formatCheck === false ? "format-write" : "format-check"];
3940
+ case "test":
3941
+ return [input.noRun ? "test-compile" : "test"];
3942
+ case "build":
3943
+ return ["build"];
3944
+ case "debug":
3945
+ if (input.debug === "race") return ["debug-race"];
3946
+ if (input.debug === "test") return ["test-compile", "test"];
3947
+ if (input.debug === "runtime") return ["semantic", "build", "test"];
3948
+ return ["semantic"];
3949
+ }
3950
+ }
3951
+ function operationOptions(input) {
3952
+ return {
3953
+ ...input.target ? { target: input.target } : {},
3954
+ ...input.filter ? { filter: input.filter } : {},
3955
+ ...input.coverage !== void 0 ? { coverage: input.coverage } : {},
3956
+ ...input.noRun !== void 0 ? { noRun: input.noRun } : {}
3957
+ };
3958
+ }
3959
+ function unavailableOutput(reason) {
3960
+ return {
3961
+ status: "unavailable",
3962
+ runs: [],
3963
+ diagnostics: Object.freeze([]),
3964
+ summary: { runs: 0, passed: 0, failed: 0, errors: 0, warnings: 0 },
3965
+ output: reason
3966
+ };
84
3967
  }
3968
+ function aggregateRuns(runs, unavailableReasons) {
3969
+ if (runs.length === 0)
3970
+ return unavailableOutput(
3971
+ unavailableReasons.join("\n") || "No executable language operations were planned."
3972
+ );
3973
+ const diagnostics = Object.freeze(runs.flatMap((run) => [...run.diagnostics]));
3974
+ const failed = runs.filter((run) => run.status === "failed").length;
3975
+ const terminal = runs.find((run) => run.status === "cancelled" || run.status === "timed_out");
3976
+ const unavailable2 = runs.filter((run) => run.status === "unavailable").length;
3977
+ const status = terminal?.status ?? (failed > 0 ? "failed" : unavailableReasons.length > 0 || unavailable2 === runs.length ? "unavailable" : "passed");
3978
+ return {
3979
+ status,
3980
+ language: runs[0].language,
3981
+ workspace: runs[0].workspace.root,
3982
+ runs,
3983
+ diagnostics,
3984
+ summary: {
3985
+ runs: runs.length,
3986
+ passed: runs.filter((run) => run.status === "passed").length,
3987
+ failed,
3988
+ errors: diagnostics.filter((item) => item.severity === "error").length,
3989
+ warnings: diagnostics.filter((item) => item.severity === "warning").length
3990
+ },
3991
+ output: [
3992
+ ...runs.map(
3993
+ (run) => `${run.plan.operation}: ${run.status}${run.output ? `
3994
+ ${run.output}` : ""}`
3995
+ ),
3996
+ ...unavailableReasons
3997
+ ].join("\n\n")
3998
+ };
3999
+ }
4000
+ var LANGUAGE_IDS, languageTool;
4001
+ var init_execute_tool = __esm({
4002
+ "src/languages/execute-tool.ts"() {
4003
+ "use strict";
4004
+ init_util();
4005
+ init_execute();
4006
+ init_plan();
4007
+ LANGUAGE_IDS = [
4008
+ "typescript",
4009
+ "javascript",
4010
+ "go",
4011
+ "rust",
4012
+ "php",
4013
+ "csharp",
4014
+ "python",
4015
+ "java",
4016
+ "ruby",
4017
+ "c",
4018
+ "cpp",
4019
+ "swift",
4020
+ "dart",
4021
+ "elixir",
4022
+ "deno",
4023
+ "shell"
4024
+ ];
4025
+ languageTool = {
4026
+ name: "language",
4027
+ category: "Code Quality",
4028
+ description: "Execute predefined language-specific checks, linters, formatters, tests, builds, and debugging evidence.",
4029
+ 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.",
4030
+ selection: {
4031
+ doNotUseWhen: "You only need workspace detection/planning, or need to modify dependencies.",
4032
+ useInstead: ["language_info", "install", "audit", "outdated"]
4033
+ },
4034
+ permission: "confirm",
4035
+ mutating: true,
4036
+ riskTier: "standard",
4037
+ capabilities: ["shell.restricted", "fs.write"],
4038
+ icon: "code",
4039
+ timeoutMs: 6e5,
4040
+ inputSchema: {
4041
+ type: "object",
4042
+ properties: {
4043
+ action: {
4044
+ type: "string",
4045
+ enum: ["check", "lint", "format", "test", "build", "debug"],
4046
+ description: "Language operation to execute."
4047
+ },
4048
+ cwd: { type: "string", description: "Directory inside the project." },
4049
+ target: { type: "string", description: "Optional target file for workspace selection." },
4050
+ language: { type: "string", enum: LANGUAGE_IDS, description: "Optional language filter." },
4051
+ workspace: { type: "string", description: "Detected workspace id or root." },
4052
+ mode: { type: "string", enum: ["fast", "standard", "thorough"] },
4053
+ check: { type: "string", enum: ["syntax", "semantic", "all"] },
4054
+ formatCheck: {
4055
+ type: "boolean",
4056
+ description: "Verify formatting without writing (default true)."
4057
+ },
4058
+ filter: {
4059
+ type: "string",
4060
+ description: "Validated test/debug filter passed to profile adapters."
4061
+ },
4062
+ coverage: { type: "boolean" },
4063
+ noRun: { type: "boolean", description: "Compile tests without running when supported." },
4064
+ debug: { type: "string", enum: ["compile", "test", "runtime", "race"] }
4065
+ },
4066
+ required: ["action"],
4067
+ additionalProperties: false
4068
+ },
4069
+ validate(input) {
4070
+ const errors = [];
4071
+ if (input.action !== "check" && input.check)
4072
+ errors.push("check is only valid when action=check");
4073
+ if (input.action !== "format" && input.formatCheck !== void 0)
4074
+ errors.push("formatCheck is only valid when action=format");
4075
+ if (input.action !== "debug" && input.debug)
4076
+ errors.push("debug is only valid when action=debug");
4077
+ if (input.coverage) errors.push("coverage collection is not implemented in Phase 2");
4078
+ if (input.noRun && input.action !== "test") errors.push("noRun is only valid when action=test");
4079
+ if (input.filter && (input.filter.length > 500 || input.filter.startsWith("-") || /[\r\n\0;&|`$<>]/.test(input.filter)))
4080
+ errors.push(
4081
+ 'filter must be at most 500 characters, not start with "-", and contain no shell/control characters'
4082
+ );
4083
+ return errors;
4084
+ },
4085
+ async execute(input, ctx, opts) {
4086
+ let final;
4087
+ const stream = languageTool.executeStream;
4088
+ if (!stream) throw new Error("languageTool: stream execution unavailable");
4089
+ for await (const event of stream(input, ctx, opts)) {
4090
+ if (event.type === "final") final = event.output;
4091
+ }
4092
+ if (!final) throw new Error("language: stream ended without final event");
4093
+ return final;
4094
+ },
4095
+ async *executeStream(input, ctx, opts) {
4096
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
4097
+ const operations = operationsFor(input);
4098
+ const runs = [];
4099
+ const unavailableReasons = [];
4100
+ for (const operation of operations) {
4101
+ opts.signal.throwIfAborted();
4102
+ const planResult = await planLanguageOperation({
4103
+ projectRoot: ctx.projectRoot,
4104
+ cwd,
4105
+ operation,
4106
+ ...input.target ? { target: input.target } : {},
4107
+ ...input.language ? { language: input.language } : {},
4108
+ ...input.workspace ? { workspace: input.workspace } : {},
4109
+ ...input.mode ? { mode: input.mode } : {},
4110
+ operationOptions: operationOptions(input),
4111
+ signal: opts.signal
4112
+ });
4113
+ if (planResult.status !== "planned") {
4114
+ const reason = planResult.status === "unavailable" ? planResult.unavailable.reason : planResult.reason;
4115
+ if (runs.length === 0) {
4116
+ return yield {
4117
+ type: "final",
4118
+ output: unavailableOutput(reason)
4119
+ };
4120
+ }
4121
+ unavailableReasons.push(`${operation}: ${reason}`);
4122
+ yield { type: "warning", text: `${operation}: ${reason}` };
4123
+ continue;
4124
+ }
4125
+ const runner = executeLanguagePlan({
4126
+ projectRoot: ctx.projectRoot,
4127
+ workspace: planResult.workspace,
4128
+ plan: planResult.plan,
4129
+ signal: opts.signal
4130
+ });
4131
+ for (; ; ) {
4132
+ const next = await runner.next();
4133
+ if (next.done) {
4134
+ runs.push(next.value);
4135
+ if (next.value.plan.kind === "process") {
4136
+ ctx.recordSideEffect?.({
4137
+ toolUseId: `language-${Date.now()}-${runs.length}`,
4138
+ toolName: "language",
4139
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4140
+ input: {
4141
+ language: next.value.language,
4142
+ operation: next.value.plan.operation,
4143
+ command: next.value.plan.command,
4144
+ args: next.value.plan.args,
4145
+ cwd: next.value.plan.cwd
4146
+ },
4147
+ outcome: `${next.value.status}${next.value.exitCode === null ? "" : ` (exit ${next.value.exitCode})`}`,
4148
+ risk: "shell"
4149
+ });
4150
+ }
4151
+ break;
4152
+ }
4153
+ yield next.value;
4154
+ }
4155
+ const last = runs.at(-1);
4156
+ if (last.status === "cancelled" || last.status === "timed_out") break;
4157
+ if (last.status === "failed" && input.mode !== "thorough") break;
4158
+ }
4159
+ yield { type: "final", output: aggregateRuns(runs, unavailableReasons) };
4160
+ },
4161
+ serialize(output) {
4162
+ const lines = [
4163
+ `status=${output.status} runs=${output.summary.runs} passed=${output.summary.passed} failed=${output.summary.failed}`,
4164
+ `diagnostics: ${output.summary.errors} error(s), ${output.summary.warnings} warning(s)`
4165
+ ];
4166
+ for (const diagnostic of output.diagnostics.slice(0, 20)) {
4167
+ const location = diagnostic.file ? `${diagnostic.file}${diagnostic.range ? `:${diagnostic.range.start.line}:${diagnostic.range.start.column}` : ""}` : diagnostic.source;
4168
+ lines.push(
4169
+ `${diagnostic.severity.toUpperCase()} ${location}${diagnostic.code ? ` ${diagnostic.code}` : ""}: ${diagnostic.message}`
4170
+ );
4171
+ }
4172
+ if (output.diagnostics.length > 20)
4173
+ lines.push(`\u2026 ${output.diagnostics.length - 20} more diagnostic(s)`);
4174
+ if (output.output) lines.push(output.output);
4175
+ return lines.join("\n");
4176
+ }
4177
+ };
4178
+ }
4179
+ });
4180
+
4181
+ // src/languages/package-tool.ts
4182
+ function aggregateOutcome(outcome) {
4183
+ return {
4184
+ status: outcome.status,
4185
+ language: outcome.language,
4186
+ workspace: outcome.workspace.root,
4187
+ operation: outcome.operation,
4188
+ outcome,
4189
+ mutations: Object.freeze([...outcome.mutations]),
4190
+ vulnerabilities: Object.freeze([...outcome.vulnerabilities]),
4191
+ outdated: Object.freeze([...outcome.outdated]),
4192
+ manifestsChanged: [...outcome.manifestsChanged],
4193
+ lockfilesChanged: [...outcome.lockfilesChanged],
4194
+ output: outputSummary(outcome),
4195
+ ...outcome.run?.output ? { error: void 0 } : {}
4196
+ };
4197
+ }
4198
+ function outputSummary(outcome) {
4199
+ const lines = [
4200
+ `${outcome.operation} on ${outcome.workspace.root}: ${outcome.status}`,
4201
+ `manifestsChanged: ${outcome.manifestsChanged.join(", ") || "\u2205"}`,
4202
+ `lockfilesChanged: ${outcome.lockfilesChanged.join(", ") || "\u2205"}`
4203
+ ];
4204
+ for (const mutation of outcome.mutations) {
4205
+ lines.push(`+ ${mutation.name}${mutation.resolved ? `@${mutation.resolved}` : ""}`);
4206
+ }
4207
+ for (const vuln of outcome.vulnerabilities) {
4208
+ lines.push(`! ${vuln.package} (${vuln.severity})${vuln.advisory ? ` ${vuln.advisory}` : ""}`);
4209
+ }
4210
+ for (const outdated of outcome.outdated) {
4211
+ lines.push(`= ${outdated.name}: ${outdated.previous ?? "?"} \u2192 ${outdated.resolved ?? "?"}`);
4212
+ }
4213
+ if (outcome.run?.output) lines.push(outcome.run.output);
4214
+ return lines.join("\n");
4215
+ }
4216
+ var LANGUAGE_IDS2, SUPPORTED_OPERATIONS, OPERATION_TO_PLAN, languagePackageTool;
4217
+ var init_package_tool = __esm({
4218
+ "src/languages/package-tool.ts"() {
4219
+ "use strict";
4220
+ init_util();
4221
+ init_execute();
4222
+ init_plan();
4223
+ LANGUAGE_IDS2 = [
4224
+ "typescript",
4225
+ "javascript",
4226
+ "go",
4227
+ "rust",
4228
+ "php",
4229
+ "csharp",
4230
+ "python",
4231
+ "java",
4232
+ "ruby",
4233
+ "c",
4234
+ "cpp",
4235
+ "swift",
4236
+ "dart",
4237
+ "elixir",
4238
+ "deno",
4239
+ "shell"
4240
+ ];
4241
+ SUPPORTED_OPERATIONS = /* @__PURE__ */ new Set([
4242
+ "install",
4243
+ "add",
4244
+ "remove",
4245
+ "update",
4246
+ "audit",
4247
+ "outdated"
4248
+ ]);
4249
+ OPERATION_TO_PLAN = {
4250
+ install: "package-install",
4251
+ add: "package-add",
4252
+ remove: "package-remove",
4253
+ update: "package-update",
4254
+ audit: "package-audit",
4255
+ outdated: "package-outdated"
4256
+ };
4257
+ languagePackageTool = {
4258
+ name: "language_package",
4259
+ category: "Package Management",
4260
+ description: "Restore, mutate, audit, or report outdated packages via predefined ecosystem-specific plans.",
4261
+ 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.",
4262
+ selection: {
4263
+ doNotUseWhen: "You only need to inspect or plan, or need to compile/test/lint without touching dependencies.",
4264
+ useInstead: ["language_info", "language", "install", "audit", "outdated"]
4265
+ },
4266
+ permission: "confirm",
4267
+ mutating: true,
4268
+ riskTier: "destructive",
4269
+ capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],
4270
+ icon: "package",
4271
+ timeoutMs: 6e5,
4272
+ inputSchema: {
4273
+ type: "object",
4274
+ properties: {
4275
+ operation: {
4276
+ type: "string",
4277
+ enum: ["install", "add", "remove", "update", "audit", "outdated"],
4278
+ description: "Package management operation to perform."
4279
+ },
4280
+ cwd: { type: "string", description: "Directory inside the project." },
4281
+ language: {
4282
+ type: "string",
4283
+ enum: LANGUAGE_IDS2,
4284
+ description: "Optional language profile filter."
4285
+ },
4286
+ workspace: { type: "string", description: "Detected workspace id or root." },
4287
+ names: {
4288
+ type: "array",
4289
+ items: { type: "string" },
4290
+ description: "Validated package names. Required for add/remove/update; optional for install."
4291
+ },
4292
+ scope: {
4293
+ type: "string",
4294
+ enum: ["runtime", "development", "optional"],
4295
+ description: "Where to record the dependency (add/update)."
4296
+ },
4297
+ dryRun: {
4298
+ type: "boolean",
4299
+ description: "Preview the install without modifying the workspace."
4300
+ },
4301
+ allowScripts: {
4302
+ type: "boolean",
4303
+ description: "Opt in to running package lifecycle scripts (preinstall/install/postinstall). Default false."
4304
+ }
4305
+ },
4306
+ required: ["operation"],
4307
+ additionalProperties: false
4308
+ },
4309
+ validate(input) {
4310
+ const errors = [];
4311
+ if (!SUPPORTED_OPERATIONS.has(input.operation))
4312
+ errors.push(`Unsupported package operation: ${String(input.operation)}`);
4313
+ if ((input.operation === "add" || input.operation === "remove") && (!input.names || input.names.length === 0)) {
4314
+ errors.push(`operation=${input.operation} requires at least one name in names`);
4315
+ }
4316
+ for (const name of input.names ?? []) {
4317
+ if (typeof name !== "string" || !name) {
4318
+ errors.push("names must contain only non-empty strings");
4319
+ break;
4320
+ }
4321
+ }
4322
+ return errors;
4323
+ },
4324
+ async execute(input, ctx, opts) {
4325
+ let final;
4326
+ const stream = languagePackageTool.executeStream;
4327
+ if (!stream) throw new Error("languagePackageTool: stream execution unavailable");
4328
+ for await (const event of stream(input, ctx, opts)) {
4329
+ if (event.type === "final") final = event.output;
4330
+ }
4331
+ if (!final) throw new Error("language_package: stream ended without final event");
4332
+ return final;
4333
+ },
4334
+ async *executeStream(input, ctx, opts) {
4335
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
4336
+ const planOperation = OPERATION_TO_PLAN[input.operation];
4337
+ const operationOptions2 = {
4338
+ ...input.names ? { packages: input.names } : {},
4339
+ ...input.scope ? { packageScope: input.scope } : {},
4340
+ ...input.dryRun !== void 0 ? { allowScripts: input.dryRun } : { allowScripts: false },
4341
+ ...input.allowScripts !== void 0 ? { allowScripts: input.allowScripts } : {}
4342
+ };
4343
+ const planResult = await planLanguageOperation({
4344
+ projectRoot: ctx.projectRoot,
4345
+ cwd,
4346
+ operation: planOperation,
4347
+ ...input.language ? { language: input.language } : {},
4348
+ ...input.workspace ? { workspace: input.workspace } : {},
4349
+ operationOptions: operationOptions2,
4350
+ signal: opts.signal
4351
+ });
4352
+ if (planResult.status !== "planned") {
4353
+ const reason = planResult.status === "unavailable" ? planResult.unavailable.reason : planResult.reason;
4354
+ yield {
4355
+ type: "final",
4356
+ output: {
4357
+ status: "unavailable",
4358
+ mutations: [],
4359
+ vulnerabilities: [],
4360
+ outdated: [],
4361
+ manifestsChanged: [],
4362
+ lockfilesChanged: [],
4363
+ output: reason,
4364
+ error: reason
4365
+ }
4366
+ };
4367
+ return;
4368
+ }
4369
+ const runner = executePackagePlan({
4370
+ projectRoot: ctx.projectRoot,
4371
+ workspace: planResult.workspace,
4372
+ plan: planResult.plan,
4373
+ packages: input.names ?? [],
4374
+ signal: opts.signal
4375
+ });
4376
+ let outcome;
4377
+ for (; ; ) {
4378
+ const next = await runner.next();
4379
+ if (next.done) {
4380
+ outcome = next.value;
4381
+ break;
4382
+ }
4383
+ yield next.value;
4384
+ }
4385
+ if (!outcome) {
4386
+ yield {
4387
+ type: "final",
4388
+ output: {
4389
+ status: "unavailable",
4390
+ mutations: [],
4391
+ vulnerabilities: [],
4392
+ outdated: [],
4393
+ manifestsChanged: [],
4394
+ lockfilesChanged: [],
4395
+ output: "Language package plan produced no outcome."
4396
+ }
4397
+ };
4398
+ return;
4399
+ }
4400
+ const final = aggregateOutcome(outcome);
4401
+ if (outcome.run?.plan.kind === "process") {
4402
+ ctx.recordSideEffect?.({
4403
+ toolUseId: `language_package-${Date.now()}`,
4404
+ toolName: "language_package",
4405
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
4406
+ input: {
4407
+ language: outcome.language,
4408
+ operation: outcome.operation,
4409
+ command: outcome.run.plan.command,
4410
+ args: outcome.run.plan.args,
4411
+ cwd: outcome.run.plan.cwd,
4412
+ dryRun: input.dryRun ?? false
4413
+ },
4414
+ outcome: `${final.status}${outcome.run.exitCode === null ? "" : ` (exit ${outcome.run.exitCode})`}`,
4415
+ risk: "package"
4416
+ });
4417
+ }
4418
+ yield { type: "final", output: final };
4419
+ },
4420
+ serialize(output) {
4421
+ const lines = [
4422
+ `status=${output.status} operation=${output.operation ?? "unknown"} mutations=${output.mutations.length} vulnerabilities=${output.vulnerabilities.length} outdated=${output.outdated.length}`,
4423
+ `manifestsChanged: ${output.manifestsChanged.join(", ") || "\u2205"}`,
4424
+ `lockfilesChanged: ${output.lockfilesChanged.join(", ") || "\u2205"}`
4425
+ ];
4426
+ for (const mutation of output.mutations) {
4427
+ lines.push(`+ ${mutation.name}${mutation.resolved ? `@${mutation.resolved}` : ""}`);
4428
+ }
4429
+ for (const outdated of output.outdated) {
4430
+ lines.push(`= ${outdated.name}: ${outdated.previous ?? "?"} \u2192 ${outdated.resolved ?? "?"}`);
4431
+ }
4432
+ for (const vuln of output.vulnerabilities) {
4433
+ lines.push(`! ${vuln.package} (${vuln.severity}) ${vuln.advisory ?? ""}`);
4434
+ }
4435
+ if (output.output) lines.push(output.output);
4436
+ return lines.join("\n");
4437
+ }
4438
+ };
4439
+ }
4440
+ });
4441
+
4442
+ // src/languages/tool.ts
4443
+ var LANGUAGE_IDS3, OPERATIONS, languageInfoTool;
4444
+ var init_tool = __esm({
4445
+ "src/languages/tool.ts"() {
4446
+ "use strict";
4447
+ init_util();
4448
+ init_detect();
4449
+ init_plan();
4450
+ init_registry();
4451
+ LANGUAGE_IDS3 = [
4452
+ "typescript",
4453
+ "javascript",
4454
+ "go",
4455
+ "rust",
4456
+ "php",
4457
+ "csharp",
4458
+ "python",
4459
+ "java",
4460
+ "ruby",
4461
+ "c",
4462
+ "cpp",
4463
+ "swift",
4464
+ "dart",
4465
+ "elixir",
4466
+ "deno",
4467
+ "shell"
4468
+ ];
4469
+ OPERATIONS = [
4470
+ "syntax",
4471
+ "semantic",
4472
+ "lint",
4473
+ "format-check",
4474
+ "format-write",
4475
+ "test-compile",
4476
+ "test",
4477
+ "build",
4478
+ "run",
4479
+ "debug-compile",
4480
+ "debug-test",
4481
+ "debug-runtime",
4482
+ "debug-race",
4483
+ "package-install",
4484
+ "package-add",
4485
+ "package-remove",
4486
+ "package-update",
4487
+ "package-audit",
4488
+ "package-outdated"
4489
+ ];
4490
+ languageInfoTool = {
4491
+ name: "language_info",
4492
+ category: "Code Quality",
4493
+ description: "Detect language workspaces and preview predefined language-specific command plans without executing them.",
4494
+ 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.",
4495
+ selection: {
4496
+ doNotUseWhen: "You need to execute a compiler, test runner, formatter, or package manager.",
4497
+ useInstead: ["exec", "typecheck", "lint", "format", "test", "install"]
4498
+ },
4499
+ permission: "auto",
4500
+ mutating: false,
4501
+ riskTier: "safe",
4502
+ capabilities: ["fs.read"],
4503
+ icon: "code",
4504
+ timeoutMs: 3e4,
4505
+ inputSchema: {
4506
+ type: "object",
4507
+ properties: {
4508
+ action: {
4509
+ type: "string",
4510
+ enum: ["detect", "plan", "capabilities"],
4511
+ description: "Read-only action to perform."
4512
+ },
4513
+ cwd: { type: "string", description: "Directory inside the project to inspect." },
4514
+ target: { type: "string", description: "Optional target file used for workspace selection." },
4515
+ language: {
4516
+ type: "string",
4517
+ enum: LANGUAGE_IDS3,
4518
+ description: "Optional language profile filter."
4519
+ },
4520
+ workspace: { type: "string", description: "Workspace id or root returned by detect." },
4521
+ operation: {
4522
+ type: "string",
4523
+ enum: OPERATIONS,
4524
+ description: "Operation to preview when action=plan."
4525
+ },
4526
+ mode: {
4527
+ type: "string",
4528
+ enum: ["fast", "standard", "thorough"],
4529
+ description: "Planning mode."
4530
+ },
4531
+ options: {
4532
+ type: "object",
4533
+ properties: {
4534
+ target: { type: "string" },
4535
+ filter: { type: "string" },
4536
+ coverage: { type: "boolean" },
4537
+ noRun: { type: "boolean" },
4538
+ packages: { type: "array", items: { type: "string" } },
4539
+ packageScope: { type: "string", enum: ["runtime", "development", "optional"] },
4540
+ allowScripts: { type: "boolean" }
4541
+ },
4542
+ additionalProperties: false
4543
+ }
4544
+ },
4545
+ required: ["action"],
4546
+ additionalProperties: false
4547
+ },
4548
+ validate(input) {
4549
+ const errors = [];
4550
+ if (input.action === "plan" && !input.operation)
4551
+ errors.push("operation is required when action=plan");
4552
+ if (input.action !== "plan" && input.operation)
4553
+ errors.push("operation is only valid when action=plan");
4554
+ if (input.action !== "plan" && input.options)
4555
+ errors.push("options are only valid when action=plan");
4556
+ return errors;
4557
+ },
4558
+ async execute(input, ctx, opts) {
4559
+ if (input.action === "capabilities") {
4560
+ return {
4561
+ action: "capabilities",
4562
+ profiles: languageProfileRegistry.list().map((profile) => ({
4563
+ id: profile.id,
4564
+ displayName: profile.displayName,
4565
+ extensions: profile.extensions,
4566
+ operations: Object.keys(profile.operations).sort(),
4567
+ packageManagers: profile.packageManagers
4568
+ }))
4569
+ };
4570
+ }
4571
+ const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.workingDir ?? ctx.cwd;
4572
+ const common = {
4573
+ projectRoot: ctx.projectRoot,
4574
+ cwd,
4575
+ ...input.target ? { target: input.target } : {},
4576
+ ...input.language ? { language: input.language } : {},
4577
+ signal: opts.signal
4578
+ };
4579
+ if (input.action === "detect") {
4580
+ return { action: "detect", ...await detectLanguageWorkspaces(common) };
4581
+ }
4582
+ const result = await planLanguageOperation({
4583
+ ...common,
4584
+ operation: input.operation,
4585
+ ...input.workspace ? { workspace: input.workspace } : {},
4586
+ ...input.mode ? { mode: input.mode } : {},
4587
+ ...input.options ? { operationOptions: input.options } : {}
4588
+ });
4589
+ return { action: "plan", ...result };
4590
+ }
4591
+ };
4592
+ }
4593
+ });
4594
+
4595
+ // src/languages/index.ts
4596
+ var languages_exports = {};
4597
+ __export(languages_exports, {
4598
+ ADDITIONAL_LANGUAGE_PROFILES: () => ADDITIONAL_LANGUAGE_PROFILES,
4599
+ LanguageProfileRegistry: () => LanguageProfileRegistry,
4600
+ PRIMARY_LANGUAGE_PROFILES: () => PRIMARY_LANGUAGE_PROFILES,
4601
+ detectLanguageWorkspaces: () => detectLanguageWorkspaces,
4602
+ diagnosticsForInternalSyntax: () => diagnosticsForInternalSyntax,
4603
+ executeLanguagePlan: () => executeLanguagePlan,
4604
+ executePackagePlan: () => executePackagePlan,
4605
+ languageInfoTool: () => languageInfoTool,
4606
+ languagePackageTool: () => languagePackageTool,
4607
+ languageProfileRegistry: () => languageProfileRegistry,
4608
+ languageTool: () => languageTool,
4609
+ parseLanguageDiagnostics: () => parseLanguageDiagnostics,
4610
+ parsePackageReports: () => parsePackageReports,
4611
+ planLanguageOperation: () => planLanguageOperation,
4612
+ validateCommandPlan: () => validateCommandPlan,
4613
+ validateLanguageProfile: () => validateLanguageProfile
4614
+ });
4615
+ var init_languages = __esm({
4616
+ "src/languages/index.ts"() {
4617
+ "use strict";
4618
+ init_detect();
4619
+ init_diagnostics();
4620
+ init_execute();
4621
+ init_execute_tool();
4622
+ init_package_tool();
4623
+ init_plan();
4624
+ init_additional();
4625
+ init_primary();
4626
+ init_registry();
4627
+ init_tool();
4628
+ }
4629
+ });
4630
+
4631
+ // src/languages/legacy-bridge.ts
4632
+ var legacy_bridge_exports = {};
4633
+ __export(legacy_bridge_exports, {
4634
+ detectNonJsEcosystem: () => detectNonJsEcosystem,
4635
+ tryLegacyCodeOperation: () => tryLegacyCodeOperation,
4636
+ tryLegacyPackageOperation: () => tryLegacyPackageOperation
4637
+ });
4638
+ import * as fs5 from "node:fs/promises";
4639
+ import * as path8 from "node:path";
4640
+ async function detectNonJsEcosystem(cwd, projectRoot) {
4641
+ try {
4642
+ let dir = path8.resolve(cwd);
4643
+ const root = path8.resolve(projectRoot);
4644
+ for (let depth = 0; depth <= 4 && dir.startsWith(root); depth++) {
4645
+ for (const marker of NON_JS_MARKERS) {
4646
+ try {
4647
+ const s = await fs5.stat(path8.join(dir, marker.filename));
4648
+ if (s.isFile()) return marker.language;
4649
+ } catch {
4650
+ }
4651
+ }
4652
+ try {
4653
+ const entries = await fs5.readdir(dir);
4654
+ for (const entry of entries) {
4655
+ for (const suffix of NON_JS_SUFFIXES) {
4656
+ if (entry.toLowerCase().endsWith(suffix.suffix)) return suffix.language;
4657
+ }
4658
+ }
4659
+ } catch {
4660
+ }
4661
+ if (dir === root) break;
4662
+ dir = path8.dirname(dir);
4663
+ }
4664
+ } catch {
4665
+ }
4666
+ return null;
4667
+ }
4668
+ async function tryLegacyCodeOperation(operation, ctx) {
4669
+ const language = await detectNonJsEcosystem(ctx.cwd, ctx.projectRoot);
4670
+ if (!language) return null;
4671
+ const planResult = await planLanguageOperation({
4672
+ projectRoot: ctx.projectRoot,
4673
+ cwd: ctx.cwd,
4674
+ operation,
4675
+ language,
4676
+ ...ctx.target ? { target: ctx.target } : {},
4677
+ signal: ctx.signal
4678
+ });
4679
+ if (planResult.status !== "planned") return null;
4680
+ const runner = executeLanguagePlan({
4681
+ projectRoot: ctx.projectRoot,
4682
+ workspace: planResult.workspace,
4683
+ plan: planResult.plan,
4684
+ signal: ctx.signal
4685
+ });
4686
+ for (; ; ) {
4687
+ const next = await runner.next();
4688
+ if (next.done) return { language, run: next.value };
4689
+ }
4690
+ }
4691
+ async function tryLegacyPackageOperation(operation, ctx, packages = []) {
4692
+ const language = await detectNonJsEcosystem(ctx.cwd, ctx.projectRoot);
4693
+ if (!language) return null;
4694
+ const planResult = await planLanguageOperation({
4695
+ projectRoot: ctx.projectRoot,
4696
+ cwd: ctx.cwd,
4697
+ operation,
4698
+ language,
4699
+ signal: ctx.signal,
4700
+ ...packages.length > 0 ? { operationOptions: { packages: [...packages] } } : {}
4701
+ });
4702
+ if (planResult.status !== "planned") return null;
4703
+ const runner = executePackagePlan({
4704
+ projectRoot: ctx.projectRoot,
4705
+ workspace: planResult.workspace,
4706
+ plan: planResult.plan,
4707
+ packages,
4708
+ signal: ctx.signal
4709
+ });
4710
+ for (; ; ) {
4711
+ const next = await runner.next();
4712
+ if (next.done) return { language, outcome: next.value };
4713
+ }
4714
+ }
4715
+ var NON_JS_MARKERS, NON_JS_SUFFIXES;
4716
+ var init_legacy_bridge = __esm({
4717
+ "src/languages/legacy-bridge.ts"() {
4718
+ "use strict";
4719
+ init_languages();
4720
+ NON_JS_MARKERS = [
4721
+ { filename: "go.mod", language: "go" },
4722
+ { filename: "go.work", language: "go" },
4723
+ { filename: "Cargo.toml", language: "rust" },
4724
+ { filename: "composer.json", language: "php" }
4725
+ ];
4726
+ NON_JS_SUFFIXES = [
4727
+ { suffix: ".csproj", language: "csharp" },
4728
+ { suffix: ".fsproj", language: "csharp" }
4729
+ ];
4730
+ }
4731
+ });
85
4732
 
86
4733
  // src/outdated.ts
4734
+ init_util();
4735
+ init_win32_resolve();
4736
+ import { spawn as spawn3 } from "node:child_process";
4737
+ import { buildChildEnv as buildChildEnv2 } from "@wrongstack/core";
87
4738
  var outdatedTool = {
88
4739
  name: "outdated",
89
4740
  category: "Package Management",
@@ -132,6 +4783,57 @@ var outdatedTool = {
132
4783
  async execute(input, ctx, opts) {
133
4784
  const cwd = input.cwd ? safeResolve(input.cwd, ctx) : ctx.cwd;
134
4785
  const manager = await detectPackageManager(cwd);
4786
+ if (manager === "npm") {
4787
+ try {
4788
+ const { detectNonJsEcosystem: detectNonJsEcosystem2 } = await Promise.resolve().then(() => (init_legacy_bridge(), legacy_bridge_exports));
4789
+ const nonJs = await detectNonJsEcosystem2(cwd, ctx.projectRoot);
4790
+ if (nonJs) {
4791
+ const { planLanguageOperation: planLanguageOperation2, executePackagePlan: executePackagePlan2 } = await Promise.resolve().then(() => (init_languages(), languages_exports));
4792
+ const planResult = await planLanguageOperation2({
4793
+ projectRoot: ctx.projectRoot,
4794
+ cwd,
4795
+ operation: "package-outdated",
4796
+ language: nonJs,
4797
+ signal: opts.signal
4798
+ });
4799
+ if (planResult.status === "planned") {
4800
+ const runner = executePackagePlan2({
4801
+ projectRoot: ctx.projectRoot,
4802
+ workspace: planResult.workspace,
4803
+ plan: planResult.plan,
4804
+ packages: [],
4805
+ signal: opts.signal
4806
+ });
4807
+ let outcome = null;
4808
+ for (; ; ) {
4809
+ const next = await runner.next();
4810
+ if (next.done) {
4811
+ outcome = next.value;
4812
+ break;
4813
+ }
4814
+ }
4815
+ if (outcome) {
4816
+ const packages = outcome.outdated.map((o) => ({
4817
+ name: o.name,
4818
+ current: o.previous ?? "unknown",
4819
+ latest: o.resolved ?? "unknown",
4820
+ wanted: o.requested ?? o.resolved ?? "unknown",
4821
+ type: "unknown",
4822
+ location: o.name
4823
+ }));
4824
+ return {
4825
+ exit_code: outcome.run?.exitCode ?? 0,
4826
+ packages,
4827
+ total: packages.length,
4828
+ output: outcome.run?.output ?? (packages.length === 0 ? "All packages up to date" : ""),
4829
+ truncated: outcome.run?.truncated ?? false
4830
+ };
4831
+ }
4832
+ }
4833
+ }
4834
+ } catch {
4835
+ }
4836
+ }
135
4837
  const args = ["outdated", "--json"];
136
4838
  if (input.format === "table") args.push("--table");
137
4839
  if (input.include_deprecated) args.push("--include", "deprecated");
@@ -139,7 +4841,7 @@ var outdatedTool = {
139
4841
  }
140
4842
  };
141
4843
  function runOutdated(manager, args, cwd, signal) {
142
- return new Promise((resolve2) => {
4844
+ return new Promise((resolve6) => {
143
4845
  let stdout = "";
144
4846
  let stderr = "";
145
4847
  const MAX = 1e5;
@@ -148,7 +4850,14 @@ function runOutdated(manager, args, cwd, signal) {
148
4850
  const shim = needsShell ? buildWin32CmdShimInvocation(resolved, args) : null;
149
4851
  const spawnCmd = shim?.command ?? resolved;
150
4852
  const spawnArgs = shim?.args ?? args;
151
- const child = spawn(spawnCmd, spawnArgs, { cwd, signal, env: buildChildEnv(), stdio: ["ignore", "pipe", "pipe"], windowsHide: true, ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {} });
4853
+ const child = spawn3(spawnCmd, spawnArgs, {
4854
+ cwd,
4855
+ signal,
4856
+ env: buildChildEnv2(),
4857
+ stdio: ["ignore", "pipe", "pipe"],
4858
+ windowsHide: true,
4859
+ ...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
4860
+ });
152
4861
  child.stdout?.on("data", (c) => {
153
4862
  if (stdout.length < MAX) stdout += c.toString();
154
4863
  });
@@ -157,10 +4866,10 @@ function runOutdated(manager, args, cwd, signal) {
157
4866
  });
158
4867
  child.on("close", (code) => {
159
4868
  const result = parseOutdatedOutput(stdout, code ?? 0);
160
- resolve2(result);
4869
+ resolve6(result);
161
4870
  });
162
4871
  child.on("error", (e) => {
163
- resolve2({
4872
+ resolve6({
164
4873
  exit_code: 1,
165
4874
  packages: [],
166
4875
  total: 0,
@@ -204,7 +4913,7 @@ function parseOutdatedOutput(json, exitCode) {
204
4913
  truncated: json.length >= 1e5
205
4914
  };
206
4915
  }
207
-
208
- export { outdatedTool };
4916
+ export {
4917
+ outdatedTool
4918
+ };
209
4919
  //# sourceMappingURL=outdated.js.map
210
- //# sourceMappingURL=outdated.js.map