@wrongstack/tools 0.296.4 → 0.298.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 (258) hide show
  1. package/dist/_spawn-stream.d.ts +2 -0
  2. package/dist/_util.d.ts +29 -1
  3. package/dist/audit.js +128 -65
  4. package/dist/bash.js +20 -4
  5. package/dist/batch-tool-use.js +70 -7
  6. package/dist/browser/index.js +31 -1
  7. package/dist/builtin.js +883 -351
  8. package/dist/codebase-index/index.js +95 -16
  9. package/dist/codebase-index/project-server-protocol.d.ts +26 -0
  10. package/dist/codebase-index/project-server.js +76 -16
  11. package/dist/codebase-index/worker.js +36 -13
  12. package/dist/codebase-index/writer-search-helpers.d.ts +16 -0
  13. package/dist/codebase-index/writer.d.ts +13 -1
  14. package/dist/design.d.ts +1 -1
  15. package/dist/design.js +4 -3
  16. package/dist/diff.js +11 -1
  17. package/dist/e2e.js +9 -6
  18. package/dist/edit.js +9 -6
  19. package/dist/exec.js +562 -516
  20. package/dist/format.js +131 -65
  21. package/dist/git.js +22 -2
  22. package/dist/glob.js +11 -5
  23. package/dist/grep.js +160 -104
  24. package/dist/index.js +834 -290
  25. package/dist/install.js +135 -68
  26. package/dist/json.js +64 -18
  27. package/dist/kanban-board-inputs.d.ts +21 -4
  28. package/dist/kanban.js +20 -5
  29. package/dist/languages/index.js +143 -71
  30. package/dist/lint.js +128 -65
  31. package/dist/next-steps.d.ts +23 -0
  32. package/dist/next-steps.js +4 -0
  33. package/dist/outdated.js +101 -26
  34. package/dist/pack.js +883 -351
  35. package/dist/patch.js +11 -1
  36. package/dist/plan.js +32 -10
  37. package/dist/process-registry.d.ts +9 -0
  38. package/dist/process-registry.js +15 -0
  39. package/dist/ps-slash.js +15 -0
  40. package/dist/read.js +104 -23
  41. package/dist/replace.js +11 -3
  42. package/dist/scaffold.js +9 -6
  43. package/dist/session-kanban.js +21 -5
  44. package/dist/task.js +3 -1
  45. package/dist/test.js +128 -65
  46. package/dist/todo.js +3 -1
  47. package/dist/tool-tier.js +883 -351
  48. package/dist/tool-use.d.ts +1 -1
  49. package/dist/tool-use.js +28 -4
  50. package/dist/tree.d.ts +1 -0
  51. package/dist/tree.js +44 -8
  52. package/dist/typecheck.js +128 -65
  53. package/dist/write.js +9 -6
  54. package/package.json +5 -4
  55. package/dist/_concurrency.d.ts.map +0 -1
  56. package/dist/_danger-detect.d.ts.map +0 -1
  57. package/dist/_edit-match.d.ts.map +0 -1
  58. package/dist/_env.d.ts.map +0 -1
  59. package/dist/_fetch-guard.d.ts.map +0 -1
  60. package/dist/_output-spool.d.ts.map +0 -1
  61. package/dist/_redact-command.d.ts.map +0 -1
  62. package/dist/_regex.d.ts.map +0 -1
  63. package/dist/_session-shell.d.ts.map +0 -1
  64. package/dist/_shell-pick.d.ts.map +0 -1
  65. package/dist/_spawn-stream.d.ts.map +0 -1
  66. package/dist/_syntax-check.d.ts.map +0 -1
  67. package/dist/_util.d.ts.map +0 -1
  68. package/dist/_win32-resolve.d.ts.map +0 -1
  69. package/dist/audit.d.ts.map +0 -1
  70. package/dist/audit.js.map +0 -7
  71. package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
  72. package/dist/auto-proceed-loop-guard.js.map +0 -7
  73. package/dist/bash-kill-guard.d.ts.map +0 -1
  74. package/dist/bash.d.ts.map +0 -1
  75. package/dist/bash.js.map +0 -7
  76. package/dist/batch-tool-use.d.ts.map +0 -1
  77. package/dist/batch-tool-use.js.map +0 -7
  78. package/dist/browser/artifacts.d.ts.map +0 -1
  79. package/dist/browser/index.d.ts.map +0 -1
  80. package/dist/browser/index.js.map +0 -7
  81. package/dist/browser/manager.d.ts.map +0 -1
  82. package/dist/browser/network-guard-proxy.d.ts.map +0 -1
  83. package/dist/browser/security.d.ts.map +0 -1
  84. package/dist/browser/tools.d.ts.map +0 -1
  85. package/dist/browser/types.d.ts.map +0 -1
  86. package/dist/builtin.d.ts.map +0 -1
  87. package/dist/builtin.js.map +0 -7
  88. package/dist/circuit-breaker.d.ts.map +0 -1
  89. package/dist/circuit-breaker.js.map +0 -7
  90. package/dist/codebase-index/background-indexer.d.ts.map +0 -1
  91. package/dist/codebase-index/bm25.d.ts.map +0 -1
  92. package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
  93. package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
  94. package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
  95. package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
  96. package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
  97. package/dist/codebase-index/generic-parser.d.ts.map +0 -1
  98. package/dist/codebase-index/gitignore.d.ts.map +0 -1
  99. package/dist/codebase-index/go-parser.d.ts.map +0 -1
  100. package/dist/codebase-index/index-service.d.ts.map +0 -1
  101. package/dist/codebase-index/index.d.ts.map +0 -1
  102. package/dist/codebase-index/index.js.map +0 -7
  103. package/dist/codebase-index/indexer.d.ts.map +0 -1
  104. package/dist/codebase-index/json-parser.d.ts.map +0 -1
  105. package/dist/codebase-index/languages.d.ts.map +0 -1
  106. package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
  107. package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
  108. package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
  109. package/dist/codebase-index/project-server-client.d.ts.map +0 -1
  110. package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
  111. package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
  112. package/dist/codebase-index/project-server.d.ts.map +0 -1
  113. package/dist/codebase-index/project-server.js.map +0 -7
  114. package/dist/codebase-index/py-parser.d.ts.map +0 -1
  115. package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
  116. package/dist/codebase-index/rs-parser.d.ts.map +0 -1
  117. package/dist/codebase-index/schema.d.ts.map +0 -1
  118. package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
  119. package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
  120. package/dist/codebase-index/ts-parser.d.ts.map +0 -1
  121. package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
  122. package/dist/codebase-index/worker.d.ts.map +0 -1
  123. package/dist/codebase-index/worker.js.map +0 -7
  124. package/dist/codebase-index/writer-admin.d.ts.map +0 -1
  125. package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
  126. package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
  127. package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
  128. package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
  129. package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
  130. package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
  131. package/dist/codebase-index/writer-schema.d.ts.map +0 -1
  132. package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
  133. package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
  134. package/dist/codebase-index/writer.d.ts.map +0 -1
  135. package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
  136. package/dist/design.d.ts.map +0 -1
  137. package/dist/design.js.map +0 -7
  138. package/dist/diff.d.ts.map +0 -1
  139. package/dist/diff.js.map +0 -7
  140. package/dist/document.d.ts.map +0 -1
  141. package/dist/document.js.map +0 -7
  142. package/dist/e2e.d.ts.map +0 -1
  143. package/dist/e2e.js.map +0 -7
  144. package/dist/edit.d.ts.map +0 -1
  145. package/dist/edit.js.map +0 -7
  146. package/dist/exec-kill-guard.d.ts.map +0 -1
  147. package/dist/exec.d.ts.map +0 -1
  148. package/dist/exec.js.map +0 -7
  149. package/dist/fetch.d.ts.map +0 -1
  150. package/dist/fetch.js.map +0 -7
  151. package/dist/format.d.ts.map +0 -1
  152. package/dist/format.js.map +0 -7
  153. package/dist/git.d.ts.map +0 -1
  154. package/dist/git.js.map +0 -7
  155. package/dist/glob.d.ts.map +0 -1
  156. package/dist/glob.js.map +0 -7
  157. package/dist/grep.d.ts.map +0 -1
  158. package/dist/grep.js.map +0 -7
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js.map +0 -7
  161. package/dist/install.d.ts.map +0 -1
  162. package/dist/install.js.map +0 -7
  163. package/dist/json.d.ts.map +0 -1
  164. package/dist/json.js.map +0 -7
  165. package/dist/kanban-board-inputs.d.ts.map +0 -1
  166. package/dist/kanban-decomposition-actions.d.ts.map +0 -1
  167. package/dist/kanban-detail-actions.d.ts.map +0 -1
  168. package/dist/kanban-evidence-bridge.d.ts.map +0 -1
  169. package/dist/kanban-presence.d.ts.map +0 -1
  170. package/dist/kanban-split-task-handler.d.ts.map +0 -1
  171. package/dist/kanban-task-inputs.d.ts.map +0 -1
  172. package/dist/kanban-tool-results.d.ts.map +0 -1
  173. package/dist/kanban-tool-schema.d.ts.map +0 -1
  174. package/dist/kanban-tool-types.d.ts.map +0 -1
  175. package/dist/kanban.d.ts.map +0 -1
  176. package/dist/kanban.js.map +0 -7
  177. package/dist/languages/detect.d.ts.map +0 -1
  178. package/dist/languages/diagnostics.d.ts.map +0 -1
  179. package/dist/languages/execute-tool.d.ts.map +0 -1
  180. package/dist/languages/execute.d.ts.map +0 -1
  181. package/dist/languages/index.d.ts.map +0 -1
  182. package/dist/languages/index.js.map +0 -7
  183. package/dist/languages/legacy-bridge.d.ts.map +0 -1
  184. package/dist/languages/package-tool.d.ts.map +0 -1
  185. package/dist/languages/plan.d.ts.map +0 -1
  186. package/dist/languages/profile-helpers.d.ts.map +0 -1
  187. package/dist/languages/profiles/additional.d.ts.map +0 -1
  188. package/dist/languages/profiles/primary.d.ts.map +0 -1
  189. package/dist/languages/registry.d.ts.map +0 -1
  190. package/dist/languages/tool.d.ts.map +0 -1
  191. package/dist/languages/types.d.ts.map +0 -1
  192. package/dist/lint.d.ts.map +0 -1
  193. package/dist/lint.js.map +0 -7
  194. package/dist/logs.d.ts.map +0 -1
  195. package/dist/logs.js.map +0 -7
  196. package/dist/memory.d.ts.map +0 -1
  197. package/dist/memory.js.map +0 -7
  198. package/dist/mode.d.ts.map +0 -1
  199. package/dist/mode.js.map +0 -7
  200. package/dist/next-steps.d.ts.map +0 -1
  201. package/dist/next-steps.js.map +0 -7
  202. package/dist/outdated.d.ts.map +0 -1
  203. package/dist/outdated.js.map +0 -7
  204. package/dist/pack.d.ts.map +0 -1
  205. package/dist/pack.js.map +0 -7
  206. package/dist/patch.d.ts.map +0 -1
  207. package/dist/patch.js.map +0 -7
  208. package/dist/plan.d.ts.map +0 -1
  209. package/dist/plan.js.map +0 -7
  210. package/dist/process-guardian.d.ts.map +0 -1
  211. package/dist/process-registry-persistent.d.ts.map +0 -1
  212. package/dist/process-registry.d.ts.map +0 -1
  213. package/dist/process-registry.js.map +0 -7
  214. package/dist/ps-slash.d.ts.map +0 -1
  215. package/dist/ps-slash.js.map +0 -7
  216. package/dist/read.d.ts.map +0 -1
  217. package/dist/read.js.map +0 -7
  218. package/dist/replace.d.ts.map +0 -1
  219. package/dist/replace.js.map +0 -7
  220. package/dist/scaffold.d.ts.map +0 -1
  221. package/dist/scaffold.js.map +0 -7
  222. package/dist/search.d.ts.map +0 -1
  223. package/dist/search.js.map +0 -7
  224. package/dist/session-kanban.d.ts.map +0 -1
  225. package/dist/session-kanban.js.map +0 -7
  226. package/dist/set-working-dir.d.ts.map +0 -1
  227. package/dist/skill.d.ts.map +0 -1
  228. package/dist/skill.js.map +0 -7
  229. package/dist/spawn-background.d.ts.map +0 -1
  230. package/dist/task.d.ts.map +0 -1
  231. package/dist/task.js.map +0 -7
  232. package/dist/test.d.ts.map +0 -1
  233. package/dist/test.js.map +0 -7
  234. package/dist/todo.d.ts.map +0 -1
  235. package/dist/todo.js.map +0 -7
  236. package/dist/tool-diff.d.ts.map +0 -1
  237. package/dist/tool-diff.js.map +0 -7
  238. package/dist/tool-help.d.ts.map +0 -1
  239. package/dist/tool-help.js.map +0 -7
  240. package/dist/tool-icon-map.d.ts.map +0 -1
  241. package/dist/tool-icons.d.ts.map +0 -1
  242. package/dist/tool-icons.js.map +0 -7
  243. package/dist/tool-search.d.ts.map +0 -1
  244. package/dist/tool-search.js.map +0 -7
  245. package/dist/tool-summary.d.ts.map +0 -1
  246. package/dist/tool-summary.js.map +0 -7
  247. package/dist/tool-tier.d.ts.map +0 -1
  248. package/dist/tool-tier.js.map +0 -7
  249. package/dist/tool-use.d.ts.map +0 -1
  250. package/dist/tool-use.js.map +0 -7
  251. package/dist/tree.d.ts.map +0 -1
  252. package/dist/tree.js.map +0 -7
  253. package/dist/typecheck.d.ts.map +0 -1
  254. package/dist/typecheck.js.map +0 -7
  255. package/dist/win32.d.ts.map +0 -1
  256. package/dist/win32.js.map +0 -7
  257. package/dist/write.d.ts.map +0 -1
  258. package/dist/write.js.map +0 -7
@@ -1465,8 +1465,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
1465
1465
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
1466
1466
  }
1467
1467
  }
1468
- function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1469
- const lower = basename4.toLowerCase();
1468
+ function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
1469
+ const lower = basename5.toLowerCase();
1470
1470
  const extension = path.extname(lower);
1471
1471
  for (const profile of profiles) {
1472
1472
  const detector = profile.detectors.find(
@@ -1477,7 +1477,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1477
1477
  candidate.evidence.push({
1478
1478
  kind: detector.kind,
1479
1479
  path: fullPath,
1480
- value: basename4,
1480
+ value: basename5,
1481
1481
  weight: detector.weight
1482
1482
  });
1483
1483
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -2206,11 +2206,12 @@ function createOutputSpool(opts) {
2206
2206
  return {
2207
2207
  write(text) {
2208
2208
  if (finalized || !text) return;
2209
- totalBytes += Buffer.byteLength(text, "utf8");
2209
+ const textBytes = Buffer.byteLength(text, "utf8");
2210
+ totalBytes += textBytes;
2210
2211
  if (!stream && !failed) {
2211
- if (headBytes + text.length <= threshold) {
2212
+ if (headBytes + textBytes <= threshold) {
2212
2213
  head += text;
2213
- headBytes += text.length;
2214
+ headBytes += textBytes;
2214
2215
  return;
2215
2216
  }
2216
2217
  head += text;
@@ -2220,7 +2221,7 @@ function createOutputSpool(opts) {
2220
2221
  }
2221
2222
  if (stream) {
2222
2223
  if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
2223
- droppedBytes += Buffer.byteLength(text, "utf8");
2224
+ droppedBytes += textBytes;
2224
2225
  return;
2225
2226
  }
2226
2227
  stream.write(text);
@@ -2242,6 +2243,52 @@ function createOutputSpool(opts) {
2242
2243
  };
2243
2244
  }
2244
2245
 
2246
+ // src/_win32-resolve.ts
2247
+ import * as fs2 from "node:fs";
2248
+ import * as path4 from "node:path";
2249
+ function resolveWin32Command(cmd) {
2250
+ if (process.platform !== "win32") return cmd;
2251
+ if (cmd.includes("/") || cmd.includes("\\") || path4.extname(cmd.replace(/\//g, "\\"))) {
2252
+ return cmd;
2253
+ }
2254
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
2255
+ const pathDirs = (process.env["PATH"] ?? "").split(path4.delimiter);
2256
+ for (const dir of pathDirs) {
2257
+ const base = path4.join(dir, cmd);
2258
+ for (const ext of pathext) {
2259
+ const full = `${base}${ext}`;
2260
+ try {
2261
+ fs2.accessSync(full, fs2.constants.X_OK);
2262
+ return full;
2263
+ } catch {
2264
+ }
2265
+ }
2266
+ }
2267
+ return cmd;
2268
+ }
2269
+ var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
2270
+ function assertSafeWin32ShellArgs(args) {
2271
+ for (const arg of args) {
2272
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
2273
+ throw new Error(
2274
+ '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)
2275
+ );
2276
+ }
2277
+ }
2278
+ }
2279
+ function buildWin32CmdShimInvocation(command, args = []) {
2280
+ assertSafeWin32ShellArgs([command, ...args]);
2281
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
2282
+ return {
2283
+ command: process.env["COMSPEC"] ?? "cmd.exe",
2284
+ args: ["/d", "/c", line],
2285
+ windowsVerbatimArguments: true
2286
+ };
2287
+ }
2288
+ function quoteWin32CmdArg(arg) {
2289
+ return `"${arg}"`;
2290
+ }
2291
+
2245
2292
  // src/process-registry.ts
2246
2293
  import { spawn } from "node:child_process";
2247
2294
  import * as os from "node:os";
@@ -2573,6 +2620,7 @@ var ProcessRegistryImpl = class {
2573
2620
  }
2574
2621
  /** Get all tracked processes. */
2575
2622
  list() {
2623
+ this._pruneAllStale();
2576
2624
  return Array.from(this.processes.values());
2577
2625
  }
2578
2626
  /** Get processes filtered by name (e.g. 'bash', 'exec'). */
@@ -2603,6 +2651,7 @@ var ProcessRegistryImpl = class {
2603
2651
  * Combined stats for observability — used by /ps and the TUI status bar.
2604
2652
  */
2605
2653
  stats() {
2654
+ this._pruneAllStale();
2606
2655
  return {
2607
2656
  activeCount: this.activeCount,
2608
2657
  backgroundCount: this.activeBackgroundCount,
@@ -2842,6 +2891,19 @@ var ProcessRegistryImpl = class {
2842
2891
  this.processes.delete(pid);
2843
2892
  }
2844
2893
  }
2894
+ /**
2895
+ * Remove every stale entry, not just one PID. `list()`/`stats()` — the
2896
+ * surfaces the TUI status bar and `/ps` poll — must prune too: a child
2897
+ * whose 'close' event never fires (e.g. Windows grandchildren holding stdio
2898
+ * open) would otherwise linger in the registry until someone looks up its
2899
+ * exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the
2900
+ * wrong process. RAM-leak audit 2026-07-31, MEDIUM.
2901
+ */
2902
+ _pruneAllStale() {
2903
+ for (const [pid, entry] of this.processes) {
2904
+ if (this._isStaleEntry(entry)) this.processes.delete(pid);
2905
+ }
2906
+ }
2845
2907
  };
2846
2908
  var _registry;
2847
2909
  function getProcessRegistry() {
@@ -2851,60 +2913,17 @@ function getProcessRegistry() {
2851
2913
  return _registry;
2852
2914
  }
2853
2915
 
2854
- // src/_win32-resolve.ts
2855
- import * as fs2 from "node:fs";
2856
- import * as path4 from "node:path";
2857
- function resolveWin32Command(cmd) {
2858
- if (process.platform !== "win32") return cmd;
2859
- if (cmd.includes("/") || cmd.includes("\\") || path4.extname(cmd.replace(/\//g, "\\"))) {
2860
- return cmd;
2861
- }
2862
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
2863
- const pathDirs = (process.env["PATH"] ?? "").split(path4.delimiter);
2864
- for (const dir of pathDirs) {
2865
- const base = path4.join(dir, cmd);
2866
- for (const ext of pathext) {
2867
- const full = `${base}${ext}`;
2868
- try {
2869
- fs2.accessSync(full, fs2.constants.X_OK);
2870
- return full;
2871
- } catch {
2872
- }
2873
- }
2874
- }
2875
- return cmd;
2876
- }
2877
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
2878
- function assertSafeWin32ShellArgs(args) {
2879
- for (const arg of args) {
2880
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
2881
- throw new Error(
2882
- '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)
2883
- );
2884
- }
2885
- }
2886
- }
2887
- function buildWin32CmdShimInvocation(command, args = []) {
2888
- assertSafeWin32ShellArgs([command, ...args]);
2889
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
2890
- return {
2891
- command: process.env["COMSPEC"] ?? "cmd.exe",
2892
- args: ["/d", "/c", line],
2893
- windowsVerbatimArguments: true
2894
- };
2895
- }
2896
- function quoteWin32CmdArg(arg) {
2897
- return `"${arg}"`;
2898
- }
2899
-
2900
2916
  // src/_spawn-stream.ts
2901
2917
  var isWin = process.platform === "win32";
2902
2918
  async function* spawnStream(opts) {
2903
2919
  const max = opts.maxBytes ?? 2e5;
2904
2920
  const flushAt = opts.flushBytes ?? 4 * 1024;
2905
2921
  const maxQueue = opts.maxQueueSize ?? 500;
2922
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
2906
2923
  let stdout = "";
2907
2924
  let stderr = "";
2925
+ let stdoutRetainedBytes = 0;
2926
+ let stderrRetainedBytes = 0;
2908
2927
  let pending = "";
2909
2928
  let error;
2910
2929
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -2946,6 +2965,7 @@ async function* spawnStream(opts) {
2946
2965
  });
2947
2966
  }
2948
2967
  const queue = [];
2968
+ let queuedBytes = 0;
2949
2969
  let waiter;
2950
2970
  let paused = false;
2951
2971
  const wake = () => {
@@ -2956,7 +2976,7 @@ async function* spawnStream(opts) {
2956
2976
  }
2957
2977
  };
2958
2978
  const resume = () => {
2959
- if (paused && queue.length < maxQueue) {
2979
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
2960
2980
  paused = false;
2961
2981
  child.stdout?.resume();
2962
2982
  child.stderr?.resume();
@@ -2966,11 +2986,16 @@ async function* spawnStream(opts) {
2966
2986
  const s = c.toString();
2967
2987
  stdoutBytes += c.byteLength;
2968
2988
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
2969
- if (stdout.length < max) stdout += s;
2989
+ if (stdoutRetainedBytes < max) {
2990
+ const retained = utf8Prefix(s, max - stdoutRetainedBytes);
2991
+ stdout += retained;
2992
+ stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
2993
+ }
2970
2994
  spool.write(s);
2971
- queue.push({ kind: "out", data: s });
2995
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
2996
+ queuedBytes += c.byteLength;
2972
2997
  wake();
2973
- if (!paused && queue.length >= maxQueue) {
2998
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
2974
2999
  paused = true;
2975
3000
  child.stdout?.pause();
2976
3001
  child.stderr?.pause();
@@ -2980,11 +3005,16 @@ async function* spawnStream(opts) {
2980
3005
  const s = c.toString();
2981
3006
  stderrBytes += c.byteLength;
2982
3007
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
2983
- if (stderr.length < max) stderr += s;
3008
+ if (stderrRetainedBytes < max) {
3009
+ const retained = utf8Prefix(s, max - stderrRetainedBytes);
3010
+ stderr += retained;
3011
+ stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
3012
+ }
2984
3013
  spool.write(s);
2985
- queue.push({ kind: "err", data: s });
3014
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
3015
+ queuedBytes += c.byteLength;
2986
3016
  wake();
2987
- if (!paused && queue.length >= maxQueue) {
3017
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
2988
3018
  paused = true;
2989
3019
  child.stdout?.pause();
2990
3020
  child.stderr?.pause();
@@ -2994,7 +3024,9 @@ async function* spawnStream(opts) {
2994
3024
  child.stderr?.on("data", onErr);
2995
3025
  child.on("error", (e) => {
2996
3026
  error = e.message;
2997
- queue.push({ kind: "error", data: e.message });
3027
+ const bytes = Buffer.byteLength(e.message, "utf8");
3028
+ queue.push({ kind: "error", data: e.message, bytes });
3029
+ queuedBytes += bytes;
2998
3030
  wake();
2999
3031
  });
3000
3032
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -3015,7 +3047,13 @@ async function* spawnStream(opts) {
3015
3047
  if (typeof pid === "number") registry.unregister(pid);
3016
3048
  const exitCode2 = code ?? (signal ? 1 : 0);
3017
3049
  completeTelemetry(exitCode2, signal ?? void 0);
3018
- queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
3050
+ queue.push({
3051
+ kind: "close",
3052
+ data: "",
3053
+ bytes: 0,
3054
+ code: exitCode2,
3055
+ ...signal ? { signal } : {}
3056
+ });
3019
3057
  wake();
3020
3058
  });
3021
3059
  const onAbort = () => {
@@ -3027,7 +3065,7 @@ async function* spawnStream(opts) {
3027
3065
  } catch {
3028
3066
  }
3029
3067
  }
3030
- queue.push({ kind: "close", data: "", code: 124 });
3068
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
3031
3069
  completeTelemetry(124, "SIGKILL", true);
3032
3070
  wake();
3033
3071
  };
@@ -3045,6 +3083,7 @@ async function* spawnStream(opts) {
3045
3083
  });
3046
3084
  }
3047
3085
  const chunk = queue.shift();
3086
+ queuedBytes -= chunk.bytes;
3048
3087
  resume();
3049
3088
  if (chunk.kind === "close") {
3050
3089
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -3071,7 +3110,7 @@ async function* spawnStream(opts) {
3071
3110
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
3072
3111
  stderr,
3073
3112
  exitCode,
3074
- truncated: stdout.length >= max || stderr.length >= max,
3113
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
3075
3114
  error,
3076
3115
  spoolPath: spooled?.path,
3077
3116
  spoolBytes: spooled?.bytes
@@ -3095,6 +3134,20 @@ async function* spawnStream(opts) {
3095
3134
  }
3096
3135
  }
3097
3136
  }
3137
+ function utf8Prefix(text, maxBytes) {
3138
+ if (maxBytes <= 0) return "";
3139
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
3140
+ let low = 0;
3141
+ let high = text.length;
3142
+ while (low < high) {
3143
+ const mid = Math.ceil((low + high) / 2);
3144
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
3145
+ else high = mid - 1;
3146
+ }
3147
+ let end = low;
3148
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
3149
+ return text.slice(0, end);
3150
+ }
3098
3151
 
3099
3152
  // src/_util.ts
3100
3153
  import * as fsp2 from "node:fs/promises";
@@ -3121,12 +3174,13 @@ function ensureInsideRoot(absPath, ctx) {
3121
3174
  function safeResolve(input, ctx) {
3122
3175
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
3123
3176
  }
3124
- async function assertRealInsideRoot(absPath, ctx) {
3125
- if (ctx.allowOutsideProjectRoot) return;
3177
+ async function resolveRealInsideRoot(absPath, ctx) {
3178
+ if (ctx.allowOutsideProjectRoot) return absPath;
3126
3179
  const realRoots = await Promise.all(
3127
3180
  allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path5.resolve(r)))
3128
3181
  );
3129
3182
  let probe = absPath;
3183
+ const pendingTail = [];
3130
3184
  for (; ; ) {
3131
3185
  let real;
3132
3186
  try {
@@ -3134,13 +3188,16 @@ async function assertRealInsideRoot(absPath, ctx) {
3134
3188
  } catch (err) {
3135
3189
  if (err.code === "ENOENT") {
3136
3190
  const parent = path5.dirname(probe);
3137
- if (parent === probe) return;
3191
+ if (parent === probe) return absPath;
3192
+ pendingTail.unshift(path5.basename(probe));
3138
3193
  probe = parent;
3139
3194
  continue;
3140
3195
  }
3141
3196
  throw err;
3142
3197
  }
3143
- if (isInsideAny(real, realRoots)) return;
3198
+ if (isInsideAny(real, realRoots)) {
3199
+ return pendingTail.length > 0 ? path5.join(real, ...pendingTail) : real;
3200
+ }
3144
3201
  throw new Error(
3145
3202
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
3146
3203
  );
@@ -3148,8 +3205,7 @@ async function assertRealInsideRoot(absPath, ctx) {
3148
3205
  }
3149
3206
  async function safeResolveReal(input, ctx) {
3150
3207
  const abs = safeResolve(input, ctx);
3151
- await assertRealInsideRoot(abs, ctx);
3152
- return abs;
3208
+ return await resolveRealInsideRoot(abs, ctx);
3153
3209
  }
3154
3210
  var COMMAND_OUTPUT_MAX_BYTES = 32768;
3155
3211
  var REPEAT_RUN_THRESHOLD = 3;
@@ -3229,10 +3285,11 @@ var MAX_ARGUMENTS = 128;
3229
3285
  var MAX_ARGUMENT_LENGTH = 4096;
3230
3286
  var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
3231
3287
  var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
3232
- var GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3288
+ var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
3233
3289
  var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
3234
3290
  async function planLanguageOperation(options) {
3235
3291
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3292
+ validateFilterOption(options.operationOptions?.filter);
3236
3293
  const detection = await detectLanguageWorkspaces(options);
3237
3294
  const candidates = detection.workspaces.filter(
3238
3295
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3405,6 +3462,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3405
3462
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3406
3463
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3407
3464
  }
3465
+ function validateFilterOption(filter) {
3466
+ if (filter === void 0) return;
3467
+ if (filter.startsWith("-") || filter.includes(" --")) {
3468
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
3469
+ }
3470
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
3471
+ throw new Error("Invalid test filter (oversized or contains a control character)");
3472
+ }
3473
+ }
3408
3474
  function validateOperationOptions(operation, packages) {
3409
3475
  if (!operation.startsWith("package-") || !packages) return;
3410
3476
  for (const value of packages) {
@@ -3942,6 +4008,9 @@ var languageTool = {
3942
4008
  useInstead: ["language_info", "install", "audit", "outdated"]
3943
4009
  },
3944
4010
  permission: "confirm",
4011
+ // WS-046: gives permission decisions something to key on.
4012
+ // The action performed (build/test/lint); the cwd is where, not what.
4013
+ subjectKey: "action",
3945
4014
  mutating: true,
3946
4015
  riskTier: "standard",
3947
4016
  capabilities: ["shell.restricted", "fs.write"],
@@ -4199,6 +4268,9 @@ var languagePackageTool = {
4199
4268
  useInstead: ["language_info", "language", "install", "audit", "outdated"]
4200
4269
  },
4201
4270
  permission: "confirm",
4271
+ // WS-046: gives permission decisions something to key on.
4272
+ // The package operation performed — install/remove are not interchangeable.
4273
+ subjectKey: "operation",
4202
4274
  mutating: true,
4203
4275
  riskTier: "destructive",
4204
4276
  capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],