@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
package/dist/outdated.js CHANGED
@@ -52,12 +52,13 @@ function ensureInsideRoot(absPath, ctx) {
52
52
  function safeResolve(input, ctx) {
53
53
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
54
54
  }
55
- async function assertRealInsideRoot(absPath, ctx) {
56
- if (ctx.allowOutsideProjectRoot) return;
55
+ async function resolveRealInsideRoot(absPath, ctx) {
56
+ if (ctx.allowOutsideProjectRoot) return absPath;
57
57
  const realRoots = await Promise.all(
58
58
  allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
59
59
  );
60
60
  let probe = absPath;
61
+ const pendingTail = [];
61
62
  for (; ; ) {
62
63
  let real;
63
64
  try {
@@ -65,13 +66,16 @@ async function assertRealInsideRoot(absPath, ctx) {
65
66
  } catch (err) {
66
67
  if (err.code === "ENOENT") {
67
68
  const parent = path.dirname(probe);
68
- if (parent === probe) return;
69
+ if (parent === probe) return absPath;
70
+ pendingTail.unshift(path.basename(probe));
69
71
  probe = parent;
70
72
  continue;
71
73
  }
72
74
  throw err;
73
75
  }
74
- if (isInsideAny(real, realRoots)) return;
76
+ if (isInsideAny(real, realRoots)) {
77
+ return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
78
+ }
75
79
  throw new Error(
76
80
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
77
81
  );
@@ -79,8 +83,7 @@ async function assertRealInsideRoot(absPath, ctx) {
79
83
  }
80
84
  async function safeResolveReal(input, ctx) {
81
85
  const abs = safeResolve(input, ctx);
82
- await assertRealInsideRoot(abs, ctx);
83
- return abs;
86
+ return await resolveRealInsideRoot(abs, ctx);
84
87
  }
85
88
  function collapseCarriageReturns(text) {
86
89
  const lf = text.replace(/\r\n/g, "\n");
@@ -1682,8 +1685,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
1682
1685
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
1683
1686
  }
1684
1687
  }
1685
- function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1686
- const lower = basename4.toLowerCase();
1688
+ function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
1689
+ const lower = basename5.toLowerCase();
1687
1690
  const extension = path3.extname(lower);
1688
1691
  for (const profile of profiles) {
1689
1692
  const detector = profile.detectors.find(
@@ -1694,7 +1697,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
1694
1697
  candidate.evidence.push({
1695
1698
  kind: detector.kind,
1696
1699
  path: fullPath,
1697
- value: basename4,
1700
+ value: basename5,
1698
1701
  weight: detector.weight
1699
1702
  });
1700
1703
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -2442,11 +2445,12 @@ function createOutputSpool(opts) {
2442
2445
  return {
2443
2446
  write(text) {
2444
2447
  if (finalized || !text) return;
2445
- totalBytes += Buffer.byteLength(text, "utf8");
2448
+ const textBytes = Buffer.byteLength(text, "utf8");
2449
+ totalBytes += textBytes;
2446
2450
  if (!stream && !failed) {
2447
- if (headBytes + text.length <= threshold) {
2451
+ if (headBytes + textBytes <= threshold) {
2448
2452
  head += text;
2449
- headBytes += text.length;
2453
+ headBytes += textBytes;
2450
2454
  return;
2451
2455
  }
2452
2456
  head += text;
@@ -2456,7 +2460,7 @@ function createOutputSpool(opts) {
2456
2460
  }
2457
2461
  if (stream) {
2458
2462
  if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
2459
- droppedBytes += Buffer.byteLength(text, "utf8");
2463
+ droppedBytes += textBytes;
2460
2464
  return;
2461
2465
  }
2462
2466
  stream.write(text);
@@ -2840,6 +2844,7 @@ var init_process_registry = __esm({
2840
2844
  }
2841
2845
  /** Get all tracked processes. */
2842
2846
  list() {
2847
+ this._pruneAllStale();
2843
2848
  return Array.from(this.processes.values());
2844
2849
  }
2845
2850
  /** Get processes filtered by name (e.g. 'bash', 'exec'). */
@@ -2870,6 +2875,7 @@ var init_process_registry = __esm({
2870
2875
  * Combined stats for observability — used by /ps and the TUI status bar.
2871
2876
  */
2872
2877
  stats() {
2878
+ this._pruneAllStale();
2873
2879
  return {
2874
2880
  activeCount: this.activeCount,
2875
2881
  backgroundCount: this.activeBackgroundCount,
@@ -3109,6 +3115,19 @@ var init_process_registry = __esm({
3109
3115
  this.processes.delete(pid);
3110
3116
  }
3111
3117
  }
3118
+ /**
3119
+ * Remove every stale entry, not just one PID. `list()`/`stats()` — the
3120
+ * surfaces the TUI status bar and `/ps` poll — must prune too: a child
3121
+ * whose 'close' event never fires (e.g. Windows grandchildren holding stdio
3122
+ * open) would otherwise linger in the registry until someone looks up its
3123
+ * exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the
3124
+ * wrong process. RAM-leak audit 2026-07-31, MEDIUM.
3125
+ */
3126
+ _pruneAllStale() {
3127
+ for (const [pid, entry] of this.processes) {
3128
+ if (this._isStaleEntry(entry)) this.processes.delete(pid);
3129
+ }
3130
+ }
3112
3131
  };
3113
3132
  }
3114
3133
  });
@@ -3125,8 +3144,11 @@ async function* spawnStream(opts) {
3125
3144
  const max = opts.maxBytes ?? 2e5;
3126
3145
  const flushAt = opts.flushBytes ?? 4 * 1024;
3127
3146
  const maxQueue = opts.maxQueueSize ?? 500;
3147
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
3128
3148
  let stdout = "";
3129
3149
  let stderr = "";
3150
+ let stdoutRetainedBytes = 0;
3151
+ let stderrRetainedBytes = 0;
3130
3152
  let pending = "";
3131
3153
  let error;
3132
3154
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -3168,6 +3190,7 @@ async function* spawnStream(opts) {
3168
3190
  });
3169
3191
  }
3170
3192
  const queue = [];
3193
+ let queuedBytes = 0;
3171
3194
  let waiter;
3172
3195
  let paused = false;
3173
3196
  const wake = () => {
@@ -3178,7 +3201,7 @@ async function* spawnStream(opts) {
3178
3201
  }
3179
3202
  };
3180
3203
  const resume = () => {
3181
- if (paused && queue.length < maxQueue) {
3204
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
3182
3205
  paused = false;
3183
3206
  child.stdout?.resume();
3184
3207
  child.stderr?.resume();
@@ -3188,11 +3211,16 @@ async function* spawnStream(opts) {
3188
3211
  const s = c.toString();
3189
3212
  stdoutBytes += c.byteLength;
3190
3213
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
3191
- if (stdout.length < max) stdout += s;
3214
+ if (stdoutRetainedBytes < max) {
3215
+ const retained = utf8Prefix(s, max - stdoutRetainedBytes);
3216
+ stdout += retained;
3217
+ stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
3218
+ }
3192
3219
  spool.write(s);
3193
- queue.push({ kind: "out", data: s });
3220
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
3221
+ queuedBytes += c.byteLength;
3194
3222
  wake();
3195
- if (!paused && queue.length >= maxQueue) {
3223
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
3196
3224
  paused = true;
3197
3225
  child.stdout?.pause();
3198
3226
  child.stderr?.pause();
@@ -3202,11 +3230,16 @@ async function* spawnStream(opts) {
3202
3230
  const s = c.toString();
3203
3231
  stderrBytes += c.byteLength;
3204
3232
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
3205
- if (stderr.length < max) stderr += s;
3233
+ if (stderrRetainedBytes < max) {
3234
+ const retained = utf8Prefix(s, max - stderrRetainedBytes);
3235
+ stderr += retained;
3236
+ stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
3237
+ }
3206
3238
  spool.write(s);
3207
- queue.push({ kind: "err", data: s });
3239
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
3240
+ queuedBytes += c.byteLength;
3208
3241
  wake();
3209
- if (!paused && queue.length >= maxQueue) {
3242
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
3210
3243
  paused = true;
3211
3244
  child.stdout?.pause();
3212
3245
  child.stderr?.pause();
@@ -3216,7 +3249,9 @@ async function* spawnStream(opts) {
3216
3249
  child.stderr?.on("data", onErr);
3217
3250
  child.on("error", (e) => {
3218
3251
  error = e.message;
3219
- queue.push({ kind: "error", data: e.message });
3252
+ const bytes = Buffer.byteLength(e.message, "utf8");
3253
+ queue.push({ kind: "error", data: e.message, bytes });
3254
+ queuedBytes += bytes;
3220
3255
  wake();
3221
3256
  });
3222
3257
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -3237,7 +3272,13 @@ async function* spawnStream(opts) {
3237
3272
  if (typeof pid === "number") registry.unregister(pid);
3238
3273
  const exitCode2 = code ?? (signal ? 1 : 0);
3239
3274
  completeTelemetry(exitCode2, signal ?? void 0);
3240
- queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
3275
+ queue.push({
3276
+ kind: "close",
3277
+ data: "",
3278
+ bytes: 0,
3279
+ code: exitCode2,
3280
+ ...signal ? { signal } : {}
3281
+ });
3241
3282
  wake();
3242
3283
  });
3243
3284
  const onAbort = () => {
@@ -3249,7 +3290,7 @@ async function* spawnStream(opts) {
3249
3290
  } catch {
3250
3291
  }
3251
3292
  }
3252
- queue.push({ kind: "close", data: "", code: 124 });
3293
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
3253
3294
  completeTelemetry(124, "SIGKILL", true);
3254
3295
  wake();
3255
3296
  };
@@ -3267,6 +3308,7 @@ async function* spawnStream(opts) {
3267
3308
  });
3268
3309
  }
3269
3310
  const chunk = queue.shift();
3311
+ queuedBytes -= chunk.bytes;
3270
3312
  resume();
3271
3313
  if (chunk.kind === "close") {
3272
3314
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -3293,7 +3335,7 @@ async function* spawnStream(opts) {
3293
3335
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
3294
3336
  stderr,
3295
3337
  exitCode,
3296
- truncated: stdout.length >= max || stderr.length >= max,
3338
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
3297
3339
  error,
3298
3340
  spoolPath: spooled?.path,
3299
3341
  spoolBytes: spooled?.bytes
@@ -3317,13 +3359,27 @@ async function* spawnStream(opts) {
3317
3359
  }
3318
3360
  }
3319
3361
  }
3362
+ function utf8Prefix(text, maxBytes) {
3363
+ if (maxBytes <= 0) return "";
3364
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
3365
+ let low = 0;
3366
+ let high = text.length;
3367
+ while (low < high) {
3368
+ const mid = Math.ceil((low + high) / 2);
3369
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
3370
+ else high = mid - 1;
3371
+ }
3372
+ let end = low;
3373
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
3374
+ return text.slice(0, end);
3375
+ }
3320
3376
  var isWin;
3321
3377
  var init_spawn_stream = __esm({
3322
3378
  "src/_spawn-stream.ts"() {
3323
3379
  "use strict";
3324
3380
  init_output_spool();
3325
- init_process_registry();
3326
3381
  init_win32_resolve();
3382
+ init_process_registry();
3327
3383
  isWin = process.platform === "win32";
3328
3384
  }
3329
3385
  });
@@ -3333,6 +3389,7 @@ import * as fs3 from "node:fs/promises";
3333
3389
  import * as path6 from "node:path";
3334
3390
  async function planLanguageOperation(options) {
3335
3391
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3392
+ validateFilterOption(options.operationOptions?.filter);
3336
3393
  const detection = await detectLanguageWorkspaces(options);
3337
3394
  const candidates = detection.workspaces.filter(
3338
3395
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3505,6 +3562,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3505
3562
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3506
3563
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3507
3564
  }
3565
+ function validateFilterOption(filter) {
3566
+ if (filter === void 0) return;
3567
+ if (filter.startsWith("-") || filter.includes(" --")) {
3568
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
3569
+ }
3570
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
3571
+ throw new Error("Invalid test filter (oversized or contains a control character)");
3572
+ }
3573
+ }
3508
3574
  function validateOperationOptions(operation, packages) {
3509
3575
  if (!operation.startsWith("package-") || !packages) return;
3510
3576
  for (const value of packages) {
@@ -3546,7 +3612,7 @@ var init_plan = __esm({
3546
3612
  MAX_ARGUMENT_LENGTH = 4096;
3547
3613
  PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
3548
3614
  COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
3549
- GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3615
+ GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
3550
3616
  PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
3551
3617
  }
3552
3618
  });
@@ -4144,6 +4210,9 @@ var init_execute_tool = __esm({
4144
4210
  useInstead: ["language_info", "install", "audit", "outdated"]
4145
4211
  },
4146
4212
  permission: "confirm",
4213
+ // WS-046: gives permission decisions something to key on.
4214
+ // The action performed (build/test/lint); the cwd is where, not what.
4215
+ subjectKey: "action",
4147
4216
  mutating: true,
4148
4217
  riskTier: "standard",
4149
4218
  capabilities: ["shell.restricted", "fs.write"],
@@ -4376,6 +4445,9 @@ var init_package_tool = __esm({
4376
4445
  useInstead: ["language_info", "language", "install", "audit", "outdated"]
4377
4446
  },
4378
4447
  permission: "confirm",
4448
+ // WS-046: gives permission decisions something to key on.
4449
+ // The package operation performed — install/remove are not interchangeable.
4450
+ subjectKey: "operation",
4379
4451
  mutating: true,
4380
4452
  riskTier: "destructive",
4381
4453
  capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],
@@ -4879,6 +4951,9 @@ var outdatedTool = {
4879
4951
  // tool.confirm_needed flow on every invocation. M-1 originally
4880
4952
  // fixed four sibling tools (mcp_control, shellcheck, shellcheck (scan mode),
4881
4953
  // search) but missed this one; applying the same contract here.
4954
+ // WS-046: gives permission decisions something to key on.
4955
+ // The working directory scanned.
4956
+ subjectKey: "cwd",
4882
4957
  mutating: true,
4883
4958
  // Capability is outbound network — the tool only hits the package
4884
4959
  // registry over HTTP, never touches the filesystem or runs shell.