@wrongstack/tools 0.297.0 → 0.298.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/dist/_spawn-stream.d.ts +2 -0
  2. package/dist/_util.d.ts +29 -1
  3. package/dist/audit.js +113 -65
  4. package/dist/bash.js +5 -4
  5. package/dist/batch-tool-use.js +70 -7
  6. package/dist/browser/index.js +31 -1
  7. package/dist/builtin.js +908 -390
  8. package/dist/codebase-index/index.js +68 -16
  9. package/dist/codebase-index/project-server-protocol.d.ts +26 -0
  10. package/dist/codebase-index/project-server.js +60 -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-allowlist.d.ts +15 -0
  20. package/dist/exec.js +627 -568
  21. package/dist/format.js +116 -65
  22. package/dist/git.js +22 -2
  23. package/dist/glob.js +11 -5
  24. package/dist/grep.js +160 -104
  25. package/dist/index.js +859 -329
  26. package/dist/install.js +120 -68
  27. package/dist/json.js +64 -18
  28. package/dist/kanban-board-inputs.d.ts +21 -4
  29. package/dist/kanban.js +20 -5
  30. package/dist/languages/index.js +128 -71
  31. package/dist/lint.js +113 -65
  32. package/dist/outdated.js +86 -26
  33. package/dist/pack.js +908 -390
  34. package/dist/patch.js +11 -1
  35. package/dist/plan.js +32 -10
  36. package/dist/read.js +77 -23
  37. package/dist/replace.js +11 -3
  38. package/dist/scaffold.js +9 -6
  39. package/dist/session-kanban.js +21 -5
  40. package/dist/task.js +3 -1
  41. package/dist/test.js +113 -65
  42. package/dist/todo.js +3 -1
  43. package/dist/tool-tier.js +908 -390
  44. package/dist/tool-use.d.ts +1 -1
  45. package/dist/tool-use.js +28 -4
  46. package/dist/tree.d.ts +1 -0
  47. package/dist/tree.js +44 -8
  48. package/dist/typecheck.js +113 -65
  49. package/dist/write.js +9 -6
  50. package/package.json +7 -6
  51. package/dist/_concurrency.d.ts.map +0 -1
  52. package/dist/_danger-detect.d.ts.map +0 -1
  53. package/dist/_edit-match.d.ts.map +0 -1
  54. package/dist/_env.d.ts.map +0 -1
  55. package/dist/_fetch-guard.d.ts.map +0 -1
  56. package/dist/_output-spool.d.ts.map +0 -1
  57. package/dist/_redact-command.d.ts.map +0 -1
  58. package/dist/_regex.d.ts.map +0 -1
  59. package/dist/_session-shell.d.ts.map +0 -1
  60. package/dist/_shell-pick.d.ts.map +0 -1
  61. package/dist/_spawn-stream.d.ts.map +0 -1
  62. package/dist/_syntax-check.d.ts.map +0 -1
  63. package/dist/_util.d.ts.map +0 -1
  64. package/dist/_win32-resolve.d.ts.map +0 -1
  65. package/dist/audit.d.ts.map +0 -1
  66. package/dist/audit.js.map +0 -7
  67. package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
  68. package/dist/auto-proceed-loop-guard.js.map +0 -7
  69. package/dist/bash-kill-guard.d.ts.map +0 -1
  70. package/dist/bash.d.ts.map +0 -1
  71. package/dist/bash.js.map +0 -7
  72. package/dist/batch-tool-use.d.ts.map +0 -1
  73. package/dist/batch-tool-use.js.map +0 -7
  74. package/dist/browser/artifacts.d.ts.map +0 -1
  75. package/dist/browser/index.d.ts.map +0 -1
  76. package/dist/browser/index.js.map +0 -7
  77. package/dist/browser/manager.d.ts.map +0 -1
  78. package/dist/browser/network-guard-proxy.d.ts.map +0 -1
  79. package/dist/browser/security.d.ts.map +0 -1
  80. package/dist/browser/tools.d.ts.map +0 -1
  81. package/dist/browser/types.d.ts.map +0 -1
  82. package/dist/builtin.d.ts.map +0 -1
  83. package/dist/builtin.js.map +0 -7
  84. package/dist/circuit-breaker.d.ts.map +0 -1
  85. package/dist/circuit-breaker.js.map +0 -7
  86. package/dist/codebase-index/background-indexer.d.ts.map +0 -1
  87. package/dist/codebase-index/bm25.d.ts.map +0 -1
  88. package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
  89. package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
  90. package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
  91. package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
  92. package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
  93. package/dist/codebase-index/generic-parser.d.ts.map +0 -1
  94. package/dist/codebase-index/gitignore.d.ts.map +0 -1
  95. package/dist/codebase-index/go-parser.d.ts.map +0 -1
  96. package/dist/codebase-index/index-service.d.ts.map +0 -1
  97. package/dist/codebase-index/index.d.ts.map +0 -1
  98. package/dist/codebase-index/index.js.map +0 -7
  99. package/dist/codebase-index/indexer.d.ts.map +0 -1
  100. package/dist/codebase-index/json-parser.d.ts.map +0 -1
  101. package/dist/codebase-index/languages.d.ts.map +0 -1
  102. package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
  103. package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
  104. package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
  105. package/dist/codebase-index/project-server-client.d.ts.map +0 -1
  106. package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
  107. package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
  108. package/dist/codebase-index/project-server.d.ts.map +0 -1
  109. package/dist/codebase-index/project-server.js.map +0 -7
  110. package/dist/codebase-index/py-parser.d.ts.map +0 -1
  111. package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
  112. package/dist/codebase-index/rs-parser.d.ts.map +0 -1
  113. package/dist/codebase-index/schema.d.ts.map +0 -1
  114. package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
  115. package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
  116. package/dist/codebase-index/ts-parser.d.ts.map +0 -1
  117. package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
  118. package/dist/codebase-index/worker.d.ts.map +0 -1
  119. package/dist/codebase-index/worker.js.map +0 -7
  120. package/dist/codebase-index/writer-admin.d.ts.map +0 -1
  121. package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
  122. package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
  123. package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
  124. package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
  125. package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
  126. package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
  127. package/dist/codebase-index/writer-schema.d.ts.map +0 -1
  128. package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
  129. package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
  130. package/dist/codebase-index/writer.d.ts.map +0 -1
  131. package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
  132. package/dist/design.d.ts.map +0 -1
  133. package/dist/design.js.map +0 -7
  134. package/dist/diff.d.ts.map +0 -1
  135. package/dist/diff.js.map +0 -7
  136. package/dist/document.d.ts.map +0 -1
  137. package/dist/document.js.map +0 -7
  138. package/dist/e2e.d.ts.map +0 -1
  139. package/dist/e2e.js.map +0 -7
  140. package/dist/edit.d.ts.map +0 -1
  141. package/dist/edit.js.map +0 -7
  142. package/dist/exec-kill-guard.d.ts.map +0 -1
  143. package/dist/exec.d.ts.map +0 -1
  144. package/dist/exec.js.map +0 -7
  145. package/dist/fetch.d.ts.map +0 -1
  146. package/dist/fetch.js.map +0 -7
  147. package/dist/format.d.ts.map +0 -1
  148. package/dist/format.js.map +0 -7
  149. package/dist/git.d.ts.map +0 -1
  150. package/dist/git.js.map +0 -7
  151. package/dist/glob.d.ts.map +0 -1
  152. package/dist/glob.js.map +0 -7
  153. package/dist/grep.d.ts.map +0 -1
  154. package/dist/grep.js.map +0 -7
  155. package/dist/index.d.ts.map +0 -1
  156. package/dist/index.js.map +0 -7
  157. package/dist/install.d.ts.map +0 -1
  158. package/dist/install.js.map +0 -7
  159. package/dist/json.d.ts.map +0 -1
  160. package/dist/json.js.map +0 -7
  161. package/dist/kanban-board-inputs.d.ts.map +0 -1
  162. package/dist/kanban-decomposition-actions.d.ts.map +0 -1
  163. package/dist/kanban-detail-actions.d.ts.map +0 -1
  164. package/dist/kanban-evidence-bridge.d.ts.map +0 -1
  165. package/dist/kanban-presence.d.ts.map +0 -1
  166. package/dist/kanban-split-task-handler.d.ts.map +0 -1
  167. package/dist/kanban-task-inputs.d.ts.map +0 -1
  168. package/dist/kanban-tool-results.d.ts.map +0 -1
  169. package/dist/kanban-tool-schema.d.ts.map +0 -1
  170. package/dist/kanban-tool-types.d.ts.map +0 -1
  171. package/dist/kanban.d.ts.map +0 -1
  172. package/dist/kanban.js.map +0 -7
  173. package/dist/languages/detect.d.ts.map +0 -1
  174. package/dist/languages/diagnostics.d.ts.map +0 -1
  175. package/dist/languages/execute-tool.d.ts.map +0 -1
  176. package/dist/languages/execute.d.ts.map +0 -1
  177. package/dist/languages/index.d.ts.map +0 -1
  178. package/dist/languages/index.js.map +0 -7
  179. package/dist/languages/legacy-bridge.d.ts.map +0 -1
  180. package/dist/languages/package-tool.d.ts.map +0 -1
  181. package/dist/languages/plan.d.ts.map +0 -1
  182. package/dist/languages/profile-helpers.d.ts.map +0 -1
  183. package/dist/languages/profiles/additional.d.ts.map +0 -1
  184. package/dist/languages/profiles/primary.d.ts.map +0 -1
  185. package/dist/languages/registry.d.ts.map +0 -1
  186. package/dist/languages/tool.d.ts.map +0 -1
  187. package/dist/languages/types.d.ts.map +0 -1
  188. package/dist/lint.d.ts.map +0 -1
  189. package/dist/lint.js.map +0 -7
  190. package/dist/logs.d.ts.map +0 -1
  191. package/dist/logs.js.map +0 -7
  192. package/dist/memory.d.ts.map +0 -1
  193. package/dist/memory.js.map +0 -7
  194. package/dist/mode.d.ts.map +0 -1
  195. package/dist/mode.js.map +0 -7
  196. package/dist/next-steps.d.ts.map +0 -1
  197. package/dist/next-steps.js.map +0 -7
  198. package/dist/outdated.d.ts.map +0 -1
  199. package/dist/outdated.js.map +0 -7
  200. package/dist/pack.d.ts.map +0 -1
  201. package/dist/pack.js.map +0 -7
  202. package/dist/patch.d.ts.map +0 -1
  203. package/dist/patch.js.map +0 -7
  204. package/dist/plan.d.ts.map +0 -1
  205. package/dist/plan.js.map +0 -7
  206. package/dist/process-guardian.d.ts.map +0 -1
  207. package/dist/process-registry-persistent.d.ts.map +0 -1
  208. package/dist/process-registry.d.ts.map +0 -1
  209. package/dist/process-registry.js.map +0 -7
  210. package/dist/ps-slash.d.ts.map +0 -1
  211. package/dist/ps-slash.js.map +0 -7
  212. package/dist/read.d.ts.map +0 -1
  213. package/dist/read.js.map +0 -7
  214. package/dist/replace.d.ts.map +0 -1
  215. package/dist/replace.js.map +0 -7
  216. package/dist/scaffold.d.ts.map +0 -1
  217. package/dist/scaffold.js.map +0 -7
  218. package/dist/search.d.ts.map +0 -1
  219. package/dist/search.js.map +0 -7
  220. package/dist/session-kanban.d.ts.map +0 -1
  221. package/dist/session-kanban.js.map +0 -7
  222. package/dist/set-working-dir.d.ts.map +0 -1
  223. package/dist/skill.d.ts.map +0 -1
  224. package/dist/skill.js.map +0 -7
  225. package/dist/spawn-background.d.ts.map +0 -1
  226. package/dist/task.d.ts.map +0 -1
  227. package/dist/task.js.map +0 -7
  228. package/dist/test.d.ts.map +0 -1
  229. package/dist/test.js.map +0 -7
  230. package/dist/todo.d.ts.map +0 -1
  231. package/dist/todo.js.map +0 -7
  232. package/dist/tool-diff.d.ts.map +0 -1
  233. package/dist/tool-diff.js.map +0 -7
  234. package/dist/tool-help.d.ts.map +0 -1
  235. package/dist/tool-help.js.map +0 -7
  236. package/dist/tool-icon-map.d.ts.map +0 -1
  237. package/dist/tool-icons.d.ts.map +0 -1
  238. package/dist/tool-icons.js.map +0 -7
  239. package/dist/tool-search.d.ts.map +0 -1
  240. package/dist/tool-search.js.map +0 -7
  241. package/dist/tool-summary.d.ts.map +0 -1
  242. package/dist/tool-summary.js.map +0 -7
  243. package/dist/tool-tier.d.ts.map +0 -1
  244. package/dist/tool-tier.js.map +0 -7
  245. package/dist/tool-use.d.ts.map +0 -1
  246. package/dist/tool-use.js.map +0 -7
  247. package/dist/tree.d.ts.map +0 -1
  248. package/dist/tree.js.map +0 -7
  249. package/dist/typecheck.d.ts.map +0 -1
  250. package/dist/typecheck.js.map +0 -7
  251. package/dist/win32.d.ts.map +0 -1
  252. package/dist/win32.js.map +0 -7
  253. package/dist/write.d.ts.map +0 -1
  254. 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";
@@ -2866,60 +2913,17 @@ function getProcessRegistry() {
2866
2913
  return _registry;
2867
2914
  }
2868
2915
 
2869
- // src/_win32-resolve.ts
2870
- import * as fs2 from "node:fs";
2871
- import * as path4 from "node:path";
2872
- function resolveWin32Command(cmd) {
2873
- if (process.platform !== "win32") return cmd;
2874
- if (cmd.includes("/") || cmd.includes("\\") || path4.extname(cmd.replace(/\//g, "\\"))) {
2875
- return cmd;
2876
- }
2877
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
2878
- const pathDirs = (process.env["PATH"] ?? "").split(path4.delimiter);
2879
- for (const dir of pathDirs) {
2880
- const base = path4.join(dir, cmd);
2881
- for (const ext of pathext) {
2882
- const full = `${base}${ext}`;
2883
- try {
2884
- fs2.accessSync(full, fs2.constants.X_OK);
2885
- return full;
2886
- } catch {
2887
- }
2888
- }
2889
- }
2890
- return cmd;
2891
- }
2892
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
2893
- function assertSafeWin32ShellArgs(args) {
2894
- for (const arg of args) {
2895
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
2896
- throw new Error(
2897
- '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)
2898
- );
2899
- }
2900
- }
2901
- }
2902
- function buildWin32CmdShimInvocation(command, args = []) {
2903
- assertSafeWin32ShellArgs([command, ...args]);
2904
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
2905
- return {
2906
- command: process.env["COMSPEC"] ?? "cmd.exe",
2907
- args: ["/d", "/c", line],
2908
- windowsVerbatimArguments: true
2909
- };
2910
- }
2911
- function quoteWin32CmdArg(arg) {
2912
- return `"${arg}"`;
2913
- }
2914
-
2915
2916
  // src/_spawn-stream.ts
2916
2917
  var isWin = process.platform === "win32";
2917
2918
  async function* spawnStream(opts) {
2918
2919
  const max = opts.maxBytes ?? 2e5;
2919
2920
  const flushAt = opts.flushBytes ?? 4 * 1024;
2920
2921
  const maxQueue = opts.maxQueueSize ?? 500;
2922
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
2921
2923
  let stdout = "";
2922
2924
  let stderr = "";
2925
+ let stdoutRetainedBytes = 0;
2926
+ let stderrRetainedBytes = 0;
2923
2927
  let pending = "";
2924
2928
  let error;
2925
2929
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -2961,6 +2965,7 @@ async function* spawnStream(opts) {
2961
2965
  });
2962
2966
  }
2963
2967
  const queue = [];
2968
+ let queuedBytes = 0;
2964
2969
  let waiter;
2965
2970
  let paused = false;
2966
2971
  const wake = () => {
@@ -2971,7 +2976,7 @@ async function* spawnStream(opts) {
2971
2976
  }
2972
2977
  };
2973
2978
  const resume = () => {
2974
- if (paused && queue.length < maxQueue) {
2979
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
2975
2980
  paused = false;
2976
2981
  child.stdout?.resume();
2977
2982
  child.stderr?.resume();
@@ -2981,11 +2986,16 @@ async function* spawnStream(opts) {
2981
2986
  const s = c.toString();
2982
2987
  stdoutBytes += c.byteLength;
2983
2988
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
2984
- 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
+ }
2985
2994
  spool.write(s);
2986
- queue.push({ kind: "out", data: s });
2995
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
2996
+ queuedBytes += c.byteLength;
2987
2997
  wake();
2988
- if (!paused && queue.length >= maxQueue) {
2998
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
2989
2999
  paused = true;
2990
3000
  child.stdout?.pause();
2991
3001
  child.stderr?.pause();
@@ -2995,11 +3005,16 @@ async function* spawnStream(opts) {
2995
3005
  const s = c.toString();
2996
3006
  stderrBytes += c.byteLength;
2997
3007
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
2998
- 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
+ }
2999
3013
  spool.write(s);
3000
- queue.push({ kind: "err", data: s });
3014
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
3015
+ queuedBytes += c.byteLength;
3001
3016
  wake();
3002
- if (!paused && queue.length >= maxQueue) {
3017
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
3003
3018
  paused = true;
3004
3019
  child.stdout?.pause();
3005
3020
  child.stderr?.pause();
@@ -3009,7 +3024,9 @@ async function* spawnStream(opts) {
3009
3024
  child.stderr?.on("data", onErr);
3010
3025
  child.on("error", (e) => {
3011
3026
  error = e.message;
3012
- 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;
3013
3030
  wake();
3014
3031
  });
3015
3032
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -3030,7 +3047,13 @@ async function* spawnStream(opts) {
3030
3047
  if (typeof pid === "number") registry.unregister(pid);
3031
3048
  const exitCode2 = code ?? (signal ? 1 : 0);
3032
3049
  completeTelemetry(exitCode2, signal ?? void 0);
3033
- 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
+ });
3034
3057
  wake();
3035
3058
  });
3036
3059
  const onAbort = () => {
@@ -3042,7 +3065,7 @@ async function* spawnStream(opts) {
3042
3065
  } catch {
3043
3066
  }
3044
3067
  }
3045
- queue.push({ kind: "close", data: "", code: 124 });
3068
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
3046
3069
  completeTelemetry(124, "SIGKILL", true);
3047
3070
  wake();
3048
3071
  };
@@ -3060,6 +3083,7 @@ async function* spawnStream(opts) {
3060
3083
  });
3061
3084
  }
3062
3085
  const chunk = queue.shift();
3086
+ queuedBytes -= chunk.bytes;
3063
3087
  resume();
3064
3088
  if (chunk.kind === "close") {
3065
3089
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -3086,7 +3110,7 @@ async function* spawnStream(opts) {
3086
3110
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
3087
3111
  stderr,
3088
3112
  exitCode,
3089
- truncated: stdout.length >= max || stderr.length >= max,
3113
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
3090
3114
  error,
3091
3115
  spoolPath: spooled?.path,
3092
3116
  spoolBytes: spooled?.bytes
@@ -3110,6 +3134,20 @@ async function* spawnStream(opts) {
3110
3134
  }
3111
3135
  }
3112
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
+ }
3113
3151
 
3114
3152
  // src/_util.ts
3115
3153
  import * as fsp2 from "node:fs/promises";
@@ -3136,12 +3174,13 @@ function ensureInsideRoot(absPath, ctx) {
3136
3174
  function safeResolve(input, ctx) {
3137
3175
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
3138
3176
  }
3139
- async function assertRealInsideRoot(absPath, ctx) {
3140
- if (ctx.allowOutsideProjectRoot) return;
3177
+ async function resolveRealInsideRoot(absPath, ctx) {
3178
+ if (ctx.allowOutsideProjectRoot) return absPath;
3141
3179
  const realRoots = await Promise.all(
3142
3180
  allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path5.resolve(r)))
3143
3181
  );
3144
3182
  let probe = absPath;
3183
+ const pendingTail = [];
3145
3184
  for (; ; ) {
3146
3185
  let real;
3147
3186
  try {
@@ -3149,13 +3188,16 @@ async function assertRealInsideRoot(absPath, ctx) {
3149
3188
  } catch (err) {
3150
3189
  if (err.code === "ENOENT") {
3151
3190
  const parent = path5.dirname(probe);
3152
- if (parent === probe) return;
3191
+ if (parent === probe) return absPath;
3192
+ pendingTail.unshift(path5.basename(probe));
3153
3193
  probe = parent;
3154
3194
  continue;
3155
3195
  }
3156
3196
  throw err;
3157
3197
  }
3158
- if (isInsideAny(real, realRoots)) return;
3198
+ if (isInsideAny(real, realRoots)) {
3199
+ return pendingTail.length > 0 ? path5.join(real, ...pendingTail) : real;
3200
+ }
3159
3201
  throw new Error(
3160
3202
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
3161
3203
  );
@@ -3163,8 +3205,7 @@ async function assertRealInsideRoot(absPath, ctx) {
3163
3205
  }
3164
3206
  async function safeResolveReal(input, ctx) {
3165
3207
  const abs = safeResolve(input, ctx);
3166
- await assertRealInsideRoot(abs, ctx);
3167
- return abs;
3208
+ return await resolveRealInsideRoot(abs, ctx);
3168
3209
  }
3169
3210
  var COMMAND_OUTPUT_MAX_BYTES = 32768;
3170
3211
  var REPEAT_RUN_THRESHOLD = 3;
@@ -3244,10 +3285,11 @@ var MAX_ARGUMENTS = 128;
3244
3285
  var MAX_ARGUMENT_LENGTH = 4096;
3245
3286
  var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
3246
3287
  var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
3247
- 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;
3248
3289
  var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
3249
3290
  async function planLanguageOperation(options) {
3250
3291
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3292
+ validateFilterOption(options.operationOptions?.filter);
3251
3293
  const detection = await detectLanguageWorkspaces(options);
3252
3294
  const candidates = detection.workspaces.filter(
3253
3295
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3420,6 +3462,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3420
3462
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3421
3463
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3422
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
+ }
3423
3474
  function validateOperationOptions(operation, packages) {
3424
3475
  if (!operation.startsWith("package-") || !packages) return;
3425
3476
  for (const value of packages) {
@@ -3957,6 +4008,9 @@ var languageTool = {
3957
4008
  useInstead: ["language_info", "install", "audit", "outdated"]
3958
4009
  },
3959
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",
3960
4014
  mutating: true,
3961
4015
  riskTier: "standard",
3962
4016
  capabilities: ["shell.restricted", "fs.write"],
@@ -4214,6 +4268,9 @@ var languagePackageTool = {
4214
4268
  useInstead: ["language_info", "language", "install", "audit", "outdated"]
4215
4269
  },
4216
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",
4217
4274
  mutating: true,
4218
4275
  riskTier: "destructive",
4219
4276
  capabilities: ["shell.restricted", "fs.write", "net.outbound", "package.install"],
package/dist/lint.js CHANGED
@@ -78,11 +78,12 @@ function createOutputSpool(opts) {
78
78
  return {
79
79
  write(text) {
80
80
  if (finalized || !text) return;
81
- totalBytes += Buffer.byteLength(text, "utf8");
81
+ const textBytes = Buffer.byteLength(text, "utf8");
82
+ totalBytes += textBytes;
82
83
  if (!stream && !failed) {
83
- if (headBytes + text.length <= threshold) {
84
+ if (headBytes + textBytes <= threshold) {
84
85
  head += text;
85
- headBytes += text.length;
86
+ headBytes += textBytes;
86
87
  return;
87
88
  }
88
89
  head += text;
@@ -92,7 +93,7 @@ function createOutputSpool(opts) {
92
93
  }
93
94
  if (stream) {
94
95
  if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
95
- droppedBytes += Buffer.byteLength(text, "utf8");
96
+ droppedBytes += textBytes;
96
97
  return;
97
98
  }
98
99
  stream.write(text);
@@ -114,6 +115,52 @@ function createOutputSpool(opts) {
114
115
  };
115
116
  }
116
117
 
118
+ // src/_win32-resolve.ts
119
+ import * as fs from "node:fs";
120
+ import * as path2 from "node:path";
121
+ function resolveWin32Command(cmd) {
122
+ if (process.platform !== "win32") return cmd;
123
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
124
+ return cmd;
125
+ }
126
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
127
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
128
+ for (const dir of pathDirs) {
129
+ const base = path2.join(dir, cmd);
130
+ for (const ext of pathext) {
131
+ const full = `${base}${ext}`;
132
+ try {
133
+ fs.accessSync(full, fs.constants.X_OK);
134
+ return full;
135
+ } catch {
136
+ }
137
+ }
138
+ }
139
+ return cmd;
140
+ }
141
+ var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
142
+ function assertSafeWin32ShellArgs(args) {
143
+ for (const arg of args) {
144
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
145
+ throw new Error(
146
+ '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)
147
+ );
148
+ }
149
+ }
150
+ }
151
+ function buildWin32CmdShimInvocation(command, args = []) {
152
+ assertSafeWin32ShellArgs([command, ...args]);
153
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
154
+ return {
155
+ command: process.env["COMSPEC"] ?? "cmd.exe",
156
+ args: ["/d", "/c", line],
157
+ windowsVerbatimArguments: true
158
+ };
159
+ }
160
+ function quoteWin32CmdArg(arg) {
161
+ return `"${arg}"`;
162
+ }
163
+
117
164
  // src/process-registry.ts
118
165
  import { spawn } from "node:child_process";
119
166
  import * as os from "node:os";
@@ -738,60 +785,17 @@ function getProcessRegistry() {
738
785
  return _registry;
739
786
  }
740
787
 
741
- // src/_win32-resolve.ts
742
- import * as fs from "node:fs";
743
- import * as path2 from "node:path";
744
- function resolveWin32Command(cmd) {
745
- if (process.platform !== "win32") return cmd;
746
- if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
747
- return cmd;
748
- }
749
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
750
- const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
751
- for (const dir of pathDirs) {
752
- const base = path2.join(dir, cmd);
753
- for (const ext of pathext) {
754
- const full = `${base}${ext}`;
755
- try {
756
- fs.accessSync(full, fs.constants.X_OK);
757
- return full;
758
- } catch {
759
- }
760
- }
761
- }
762
- return cmd;
763
- }
764
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
765
- function assertSafeWin32ShellArgs(args) {
766
- for (const arg of args) {
767
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
768
- throw new Error(
769
- '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)
770
- );
771
- }
772
- }
773
- }
774
- function buildWin32CmdShimInvocation(command, args = []) {
775
- assertSafeWin32ShellArgs([command, ...args]);
776
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
777
- return {
778
- command: process.env["COMSPEC"] ?? "cmd.exe",
779
- args: ["/d", "/c", line],
780
- windowsVerbatimArguments: true
781
- };
782
- }
783
- function quoteWin32CmdArg(arg) {
784
- return `"${arg}"`;
785
- }
786
-
787
788
  // src/_spawn-stream.ts
788
789
  var isWin = process.platform === "win32";
789
790
  async function* spawnStream(opts) {
790
791
  const max = opts.maxBytes ?? 2e5;
791
792
  const flushAt = opts.flushBytes ?? 4 * 1024;
792
793
  const maxQueue = opts.maxQueueSize ?? 500;
794
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
793
795
  let stdout = "";
794
796
  let stderr = "";
797
+ let stdoutRetainedBytes = 0;
798
+ let stderrRetainedBytes = 0;
795
799
  let pending = "";
796
800
  let error;
797
801
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -833,6 +837,7 @@ async function* spawnStream(opts) {
833
837
  });
834
838
  }
835
839
  const queue = [];
840
+ let queuedBytes = 0;
836
841
  let waiter;
837
842
  let paused = false;
838
843
  const wake = () => {
@@ -843,7 +848,7 @@ async function* spawnStream(opts) {
843
848
  }
844
849
  };
845
850
  const resume = () => {
846
- if (paused && queue.length < maxQueue) {
851
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
847
852
  paused = false;
848
853
  child.stdout?.resume();
849
854
  child.stderr?.resume();
@@ -853,11 +858,16 @@ async function* spawnStream(opts) {
853
858
  const s = c.toString();
854
859
  stdoutBytes += c.byteLength;
855
860
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
856
- if (stdout.length < max) stdout += s;
861
+ if (stdoutRetainedBytes < max) {
862
+ const retained = utf8Prefix(s, max - stdoutRetainedBytes);
863
+ stdout += retained;
864
+ stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
865
+ }
857
866
  spool.write(s);
858
- queue.push({ kind: "out", data: s });
867
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
868
+ queuedBytes += c.byteLength;
859
869
  wake();
860
- if (!paused && queue.length >= maxQueue) {
870
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
861
871
  paused = true;
862
872
  child.stdout?.pause();
863
873
  child.stderr?.pause();
@@ -867,11 +877,16 @@ async function* spawnStream(opts) {
867
877
  const s = c.toString();
868
878
  stderrBytes += c.byteLength;
869
879
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
870
- if (stderr.length < max) stderr += s;
880
+ if (stderrRetainedBytes < max) {
881
+ const retained = utf8Prefix(s, max - stderrRetainedBytes);
882
+ stderr += retained;
883
+ stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
884
+ }
871
885
  spool.write(s);
872
- queue.push({ kind: "err", data: s });
886
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
887
+ queuedBytes += c.byteLength;
873
888
  wake();
874
- if (!paused && queue.length >= maxQueue) {
889
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
875
890
  paused = true;
876
891
  child.stdout?.pause();
877
892
  child.stderr?.pause();
@@ -881,7 +896,9 @@ async function* spawnStream(opts) {
881
896
  child.stderr?.on("data", onErr);
882
897
  child.on("error", (e) => {
883
898
  error = e.message;
884
- queue.push({ kind: "error", data: e.message });
899
+ const bytes = Buffer.byteLength(e.message, "utf8");
900
+ queue.push({ kind: "error", data: e.message, bytes });
901
+ queuedBytes += bytes;
885
902
  wake();
886
903
  });
887
904
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -902,7 +919,13 @@ async function* spawnStream(opts) {
902
919
  if (typeof pid === "number") registry.unregister(pid);
903
920
  const exitCode2 = code ?? (signal ? 1 : 0);
904
921
  completeTelemetry(exitCode2, signal ?? void 0);
905
- queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
922
+ queue.push({
923
+ kind: "close",
924
+ data: "",
925
+ bytes: 0,
926
+ code: exitCode2,
927
+ ...signal ? { signal } : {}
928
+ });
906
929
  wake();
907
930
  });
908
931
  const onAbort = () => {
@@ -914,7 +937,7 @@ async function* spawnStream(opts) {
914
937
  } catch {
915
938
  }
916
939
  }
917
- queue.push({ kind: "close", data: "", code: 124 });
940
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
918
941
  completeTelemetry(124, "SIGKILL", true);
919
942
  wake();
920
943
  };
@@ -932,6 +955,7 @@ async function* spawnStream(opts) {
932
955
  });
933
956
  }
934
957
  const chunk = queue.shift();
958
+ queuedBytes -= chunk.bytes;
935
959
  resume();
936
960
  if (chunk.kind === "close") {
937
961
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -958,7 +982,7 @@ async function* spawnStream(opts) {
958
982
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
959
983
  stderr,
960
984
  exitCode,
961
- truncated: stdout.length >= max || stderr.length >= max,
985
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
962
986
  error,
963
987
  spoolPath: spooled?.path,
964
988
  spoolBytes: spooled?.bytes
@@ -982,6 +1006,20 @@ async function* spawnStream(opts) {
982
1006
  }
983
1007
  }
984
1008
  }
1009
+ function utf8Prefix(text, maxBytes) {
1010
+ if (maxBytes <= 0) return "";
1011
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
1012
+ let low = 0;
1013
+ let high = text.length;
1014
+ while (low < high) {
1015
+ const mid = Math.ceil((low + high) / 2);
1016
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
1017
+ else high = mid - 1;
1018
+ }
1019
+ let end = low;
1020
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
1021
+ return text.slice(0, end);
1022
+ }
985
1023
 
986
1024
  // src/_util.ts
987
1025
  import * as path3 from "node:path";
@@ -2549,8 +2587,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
2549
2587
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
2550
2588
  }
2551
2589
  }
2552
- function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2553
- const lower = basename4.toLowerCase();
2590
+ function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
2591
+ const lower = basename5.toLowerCase();
2554
2592
  const extension = path4.extname(lower);
2555
2593
  for (const profile of profiles) {
2556
2594
  const detector = profile.detectors.find(
@@ -2561,7 +2599,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2561
2599
  candidate.evidence.push({
2562
2600
  kind: detector.kind,
2563
2601
  path: fullPath,
2564
- value: basename4,
2602
+ value: basename5,
2565
2603
  weight: detector.weight
2566
2604
  });
2567
2605
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -2981,10 +3019,11 @@ var MAX_ARGUMENTS = 128;
2981
3019
  var MAX_ARGUMENT_LENGTH = 4096;
2982
3020
  var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
2983
3021
  var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
2984
- var GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3022
+ var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
2985
3023
  var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
2986
3024
  async function planLanguageOperation(options) {
2987
3025
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3026
+ validateFilterOption(options.operationOptions?.filter);
2988
3027
  const detection = await detectLanguageWorkspaces(options);
2989
3028
  const candidates = detection.workspaces.filter(
2990
3029
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3157,6 +3196,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3157
3196
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3158
3197
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3159
3198
  }
3199
+ function validateFilterOption(filter) {
3200
+ if (filter === void 0) return;
3201
+ if (filter.startsWith("-") || filter.includes(" --")) {
3202
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
3203
+ }
3204
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
3205
+ throw new Error("Invalid test filter (oversized or contains a control character)");
3206
+ }
3207
+ }
3160
3208
  function validateOperationOptions(operation, packages) {
3161
3209
  if (!operation.startsWith("package-") || !packages) return;
3162
3210
  for (const value of packages) {