@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
package/dist/format.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) {
@@ -3486,6 +3534,9 @@ var formatTool = {
3486
3534
  description: "Format source files according to project style (Biome). Can also run in check-only mode.",
3487
3535
  usageHint: "RUN REGULARLY:\n\n- Use on changed files before committing.\n- `check: true` verifies formatting without making changes (useful in CI-like flows).\nThis project has very consistent formatting expectations. Always ensure your changes are formatted.",
3488
3536
  permission: "confirm",
3537
+ // WS-046: gives permission decisions something to key on.
3538
+ // The files being rewritten are the subject; the fixer is how, not what.
3539
+ subjectKey: "files",
3489
3540
  mutating: true,
3490
3541
  capabilities: ["fs.write", "shell.restricted"],
3491
3542
  icon: "code",
package/dist/git.js CHANGED
@@ -91,6 +91,10 @@ var gitTool = {
91
91
  // Conservative: any of these may mutate. The non-mutating commands
92
92
  // (status/log/diff/branch/fetch) are still gated on `permission: 'confirm'`
93
93
  // and `MUTATING_SUBCOMMANDS` is consulted at runtime for per-call checks.
94
+ // WS-046: gives permission decisions something to key on.
95
+ // The git subcommand (status/commit/push) is what a trust rule needs to
96
+ // distinguish; `git status` and `git push --force` must not share a subject.
97
+ subjectKey: "command",
94
98
  mutating: true,
95
99
  capabilities: ["fs.write", "shell.restricted"],
96
100
  timeoutMs: TIMEOUT_MS,
@@ -159,6 +163,8 @@ var gitTool = {
159
163
  truncated: false
160
164
  };
161
165
  }
166
+ const branchGuard = validateBranchInput(input);
167
+ if (branchGuard) return branchGuard;
162
168
  if (input.command === "worktree") {
163
169
  const guard = validateWorktreeInput(input, ctx.projectRoot);
164
170
  if (guard) return guard;
@@ -207,6 +213,18 @@ var gitTool = {
207
213
  return result;
208
214
  }
209
215
  };
216
+ function validateBranchInput(input) {
217
+ const branch = input.branch;
218
+ if (branch === void 0) return null;
219
+ if (!branch.startsWith("-") && !branch.includes(" --")) return null;
220
+ return {
221
+ command: input.command,
222
+ stdout: "",
223
+ stderr: `unsafe branch name (parsed as a git option): ${branch}`,
224
+ exitCode: 1,
225
+ truncated: false
226
+ };
227
+ }
210
228
  function validateWorktreeInput(input, projectRoot) {
211
229
  const reject = (stderr) => ({
212
230
  command: "worktree",
@@ -215,7 +233,6 @@ function validateWorktreeInput(input, projectRoot) {
215
233
  exitCode: 1,
216
234
  truncated: false
217
235
  });
218
- if (input.branch?.startsWith("-")) return reject(`unsafe branch name: ${input.branch}`);
219
236
  if (input.worktreePath?.startsWith("-")) {
220
237
  return reject(`unsafe worktree path: ${input.worktreePath}`);
221
238
  }
@@ -269,7 +286,10 @@ function buildArgs(input) {
269
286
  ...files.length ? ["--", ...files] : []
270
287
  ];
271
288
  case "branch":
272
- return input.branch ? ["branch", ...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]] : ["branch"];
289
+ return input.branch ? [
290
+ "branch",
291
+ ...input.branch.startsWith("-") || input.branch.includes(" --") ? [] : [input.branch]
292
+ ] : ["branch"];
273
293
  case "checkout":
274
294
  return [
275
295
  "checkout",
package/dist/glob.js CHANGED
@@ -101,11 +101,15 @@ function safeResolve(input, ctx) {
101
101
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
102
102
  }
103
103
  async function assertRealInsideRoot(absPath, ctx) {
104
- if (ctx.allowOutsideProjectRoot) return;
104
+ await resolveRealInsideRoot(absPath, ctx);
105
+ }
106
+ async function resolveRealInsideRoot(absPath, ctx) {
107
+ if (ctx.allowOutsideProjectRoot) return absPath;
105
108
  const realRoots = await Promise.all(
106
109
  allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path2.resolve(r)))
107
110
  );
108
111
  let probe = absPath;
112
+ const pendingTail = [];
109
113
  for (; ; ) {
110
114
  let real;
111
115
  try {
@@ -113,13 +117,16 @@ async function assertRealInsideRoot(absPath, ctx) {
113
117
  } catch (err) {
114
118
  if (err.code === "ENOENT") {
115
119
  const parent = path2.dirname(probe);
116
- if (parent === probe) return;
120
+ if (parent === probe) return absPath;
121
+ pendingTail.unshift(path2.basename(probe));
117
122
  probe = parent;
118
123
  continue;
119
124
  }
120
125
  throw err;
121
126
  }
122
- if (isInsideAny(real, realRoots)) return;
127
+ if (isInsideAny(real, realRoots)) {
128
+ return pendingTail.length > 0 ? path2.join(real, ...pendingTail) : real;
129
+ }
123
130
  throw new Error(
124
131
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
125
132
  );
@@ -127,8 +134,7 @@ async function assertRealInsideRoot(absPath, ctx) {
127
134
  }
128
135
  async function safeResolveReal(input, ctx) {
129
136
  const abs = safeResolve(input, ctx);
130
- await assertRealInsideRoot(abs, ctx);
131
- return abs;
137
+ return await resolveRealInsideRoot(abs, ctx);
132
138
  }
133
139
 
134
140
  // src/glob.ts