@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/install.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/install.ts
2
- import { join as join7 } from "node:path";
2
+ import { join as join8 } from "node:path";
3
3
  import {
4
4
  detectEcosystem as detectPackageEcosystem,
5
5
  recordPackageAction
@@ -85,11 +85,12 @@ function createOutputSpool(opts) {
85
85
  return {
86
86
  write(text) {
87
87
  if (finalized || !text) return;
88
- totalBytes += Buffer.byteLength(text, "utf8");
88
+ const textBytes = Buffer.byteLength(text, "utf8");
89
+ totalBytes += textBytes;
89
90
  if (!stream && !failed) {
90
- if (headBytes + text.length <= threshold) {
91
+ if (headBytes + textBytes <= threshold) {
91
92
  head += text;
92
- headBytes += text.length;
93
+ headBytes += textBytes;
93
94
  return;
94
95
  }
95
96
  head += text;
@@ -99,7 +100,7 @@ function createOutputSpool(opts) {
99
100
  }
100
101
  if (stream) {
101
102
  if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
102
- droppedBytes += Buffer.byteLength(text, "utf8");
103
+ droppedBytes += textBytes;
103
104
  return;
104
105
  }
105
106
  stream.write(text);
@@ -121,6 +122,52 @@ function createOutputSpool(opts) {
121
122
  };
122
123
  }
123
124
 
125
+ // src/_win32-resolve.ts
126
+ import * as fs from "node:fs";
127
+ import * as path2 from "node:path";
128
+ function resolveWin32Command(cmd) {
129
+ if (process.platform !== "win32") return cmd;
130
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
131
+ return cmd;
132
+ }
133
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
134
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
135
+ for (const dir of pathDirs) {
136
+ const base = path2.join(dir, cmd);
137
+ for (const ext of pathext) {
138
+ const full = `${base}${ext}`;
139
+ try {
140
+ fs.accessSync(full, fs.constants.X_OK);
141
+ return full;
142
+ } catch {
143
+ }
144
+ }
145
+ }
146
+ return cmd;
147
+ }
148
+ var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
149
+ function assertSafeWin32ShellArgs(args) {
150
+ for (const arg of args) {
151
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
152
+ throw new Error(
153
+ '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)
154
+ );
155
+ }
156
+ }
157
+ }
158
+ function buildWin32CmdShimInvocation(command, args = []) {
159
+ assertSafeWin32ShellArgs([command, ...args]);
160
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
161
+ return {
162
+ command: process.env["COMSPEC"] ?? "cmd.exe",
163
+ args: ["/d", "/c", line],
164
+ windowsVerbatimArguments: true
165
+ };
166
+ }
167
+ function quoteWin32CmdArg(arg) {
168
+ return `"${arg}"`;
169
+ }
170
+
124
171
  // src/process-registry.ts
125
172
  import { spawn } from "node:child_process";
126
173
  import * as os from "node:os";
@@ -745,60 +792,17 @@ function getProcessRegistry() {
745
792
  return _registry;
746
793
  }
747
794
 
748
- // src/_win32-resolve.ts
749
- import * as fs from "node:fs";
750
- import * as path2 from "node:path";
751
- function resolveWin32Command(cmd) {
752
- if (process.platform !== "win32") return cmd;
753
- if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
754
- return cmd;
755
- }
756
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
757
- const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
758
- for (const dir of pathDirs) {
759
- const base = path2.join(dir, cmd);
760
- for (const ext of pathext) {
761
- const full = `${base}${ext}`;
762
- try {
763
- fs.accessSync(full, fs.constants.X_OK);
764
- return full;
765
- } catch {
766
- }
767
- }
768
- }
769
- return cmd;
770
- }
771
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
772
- function assertSafeWin32ShellArgs(args) {
773
- for (const arg of args) {
774
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
775
- throw new Error(
776
- '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)
777
- );
778
- }
779
- }
780
- }
781
- function buildWin32CmdShimInvocation(command, args = []) {
782
- assertSafeWin32ShellArgs([command, ...args]);
783
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
784
- return {
785
- command: process.env["COMSPEC"] ?? "cmd.exe",
786
- args: ["/d", "/c", line],
787
- windowsVerbatimArguments: true
788
- };
789
- }
790
- function quoteWin32CmdArg(arg) {
791
- return `"${arg}"`;
792
- }
793
-
794
795
  // src/_spawn-stream.ts
795
796
  var isWin = process.platform === "win32";
796
797
  async function* spawnStream(opts) {
797
798
  const max = opts.maxBytes ?? 2e5;
798
799
  const flushAt = opts.flushBytes ?? 4 * 1024;
799
800
  const maxQueue = opts.maxQueueSize ?? 500;
801
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
800
802
  let stdout = "";
801
803
  let stderr = "";
804
+ let stdoutRetainedBytes = 0;
805
+ let stderrRetainedBytes = 0;
802
806
  let pending = "";
803
807
  let error;
804
808
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -840,6 +844,7 @@ async function* spawnStream(opts) {
840
844
  });
841
845
  }
842
846
  const queue = [];
847
+ let queuedBytes = 0;
843
848
  let waiter;
844
849
  let paused = false;
845
850
  const wake = () => {
@@ -850,7 +855,7 @@ async function* spawnStream(opts) {
850
855
  }
851
856
  };
852
857
  const resume = () => {
853
- if (paused && queue.length < maxQueue) {
858
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
854
859
  paused = false;
855
860
  child.stdout?.resume();
856
861
  child.stderr?.resume();
@@ -860,11 +865,16 @@ async function* spawnStream(opts) {
860
865
  const s = c.toString();
861
866
  stdoutBytes += c.byteLength;
862
867
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
863
- if (stdout.length < max) stdout += s;
868
+ if (stdoutRetainedBytes < max) {
869
+ const retained = utf8Prefix(s, max - stdoutRetainedBytes);
870
+ stdout += retained;
871
+ stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
872
+ }
864
873
  spool.write(s);
865
- queue.push({ kind: "out", data: s });
874
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
875
+ queuedBytes += c.byteLength;
866
876
  wake();
867
- if (!paused && queue.length >= maxQueue) {
877
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
868
878
  paused = true;
869
879
  child.stdout?.pause();
870
880
  child.stderr?.pause();
@@ -874,11 +884,16 @@ async function* spawnStream(opts) {
874
884
  const s = c.toString();
875
885
  stderrBytes += c.byteLength;
876
886
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
877
- if (stderr.length < max) stderr += s;
887
+ if (stderrRetainedBytes < max) {
888
+ const retained = utf8Prefix(s, max - stderrRetainedBytes);
889
+ stderr += retained;
890
+ stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
891
+ }
878
892
  spool.write(s);
879
- queue.push({ kind: "err", data: s });
893
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
894
+ queuedBytes += c.byteLength;
880
895
  wake();
881
- if (!paused && queue.length >= maxQueue) {
896
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
882
897
  paused = true;
883
898
  child.stdout?.pause();
884
899
  child.stderr?.pause();
@@ -888,7 +903,9 @@ async function* spawnStream(opts) {
888
903
  child.stderr?.on("data", onErr);
889
904
  child.on("error", (e) => {
890
905
  error = e.message;
891
- queue.push({ kind: "error", data: e.message });
906
+ const bytes = Buffer.byteLength(e.message, "utf8");
907
+ queue.push({ kind: "error", data: e.message, bytes });
908
+ queuedBytes += bytes;
892
909
  wake();
893
910
  });
894
911
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -909,7 +926,13 @@ async function* spawnStream(opts) {
909
926
  if (typeof pid === "number") registry.unregister(pid);
910
927
  const exitCode2 = code ?? (signal ? 1 : 0);
911
928
  completeTelemetry(exitCode2, signal ?? void 0);
912
- queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
929
+ queue.push({
930
+ kind: "close",
931
+ data: "",
932
+ bytes: 0,
933
+ code: exitCode2,
934
+ ...signal ? { signal } : {}
935
+ });
913
936
  wake();
914
937
  });
915
938
  const onAbort = () => {
@@ -921,7 +944,7 @@ async function* spawnStream(opts) {
921
944
  } catch {
922
945
  }
923
946
  }
924
- queue.push({ kind: "close", data: "", code: 124 });
947
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
925
948
  completeTelemetry(124, "SIGKILL", true);
926
949
  wake();
927
950
  };
@@ -939,6 +962,7 @@ async function* spawnStream(opts) {
939
962
  });
940
963
  }
941
964
  const chunk = queue.shift();
965
+ queuedBytes -= chunk.bytes;
942
966
  resume();
943
967
  if (chunk.kind === "close") {
944
968
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -965,7 +989,7 @@ async function* spawnStream(opts) {
965
989
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
966
990
  stderr,
967
991
  exitCode,
968
- truncated: stdout.length >= max || stderr.length >= max,
992
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
969
993
  error,
970
994
  spoolPath: spooled?.path,
971
995
  spoolBytes: spooled?.bytes
@@ -989,6 +1013,20 @@ async function* spawnStream(opts) {
989
1013
  }
990
1014
  }
991
1015
  }
1016
+ function utf8Prefix(text, maxBytes) {
1017
+ if (maxBytes <= 0) return "";
1018
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
1019
+ let low = 0;
1020
+ let high = text.length;
1021
+ while (low < high) {
1022
+ const mid = Math.ceil((low + high) / 2);
1023
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
1024
+ else high = mid - 1;
1025
+ }
1026
+ let end = low;
1027
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
1028
+ return text.slice(0, end);
1029
+ }
992
1030
 
993
1031
  // src/_util.ts
994
1032
  import * as path3 from "node:path";
@@ -2570,8 +2608,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
2570
2608
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
2571
2609
  }
2572
2610
  }
2573
- function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2574
- const lower = basename4.toLowerCase();
2611
+ function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
2612
+ const lower = basename5.toLowerCase();
2575
2613
  const extension = path4.extname(lower);
2576
2614
  for (const profile of profiles) {
2577
2615
  const detector = profile.detectors.find(
@@ -2582,7 +2620,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2582
2620
  candidate.evidence.push({
2583
2621
  kind: detector.kind,
2584
2622
  path: fullPath,
2585
- value: basename4,
2623
+ value: basename5,
2586
2624
  weight: detector.weight
2587
2625
  });
2588
2626
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -3201,10 +3239,11 @@ var MAX_ARGUMENTS = 128;
3201
3239
  var MAX_ARGUMENT_LENGTH = 4096;
3202
3240
  var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
3203
3241
  var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
3204
- var GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3242
+ var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
3205
3243
  var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
3206
3244
  async function planLanguageOperation(options) {
3207
3245
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3246
+ validateFilterOption(options.operationOptions?.filter);
3208
3247
  const detection = await detectLanguageWorkspaces(options);
3209
3248
  const candidates = detection.workspaces.filter(
3210
3249
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3377,6 +3416,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3377
3416
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3378
3417
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3379
3418
  }
3419
+ function validateFilterOption(filter) {
3420
+ if (filter === void 0) return;
3421
+ if (filter.startsWith("-") || filter.includes(" --")) {
3422
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
3423
+ }
3424
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
3425
+ throw new Error("Invalid test filter (oversized or contains a control character)");
3426
+ }
3427
+ }
3380
3428
  function validateOperationOptions(operation, packages) {
3381
3429
  if (!operation.startsWith("package-") || !packages) return;
3382
3430
  for (const value of packages) {
@@ -3759,6 +3807,10 @@ var installTool = {
3759
3807
  description: "Install, update or manage packages using the detected package manager (pnpm/npm/yarn). Strongly preferred over raw shell commands for dependency management because it is structured and safer.",
3760
3808
  usageHint: "ALWAYS USE THIS INSTEAD OF BASH FOR PACKAGE WORK:\n\n- Empty `packages` \u2192 normal `install` (respects lockfile).\n- Provide names \u2192 adds/updates specific packages.\n- `dry_run: true` for safe preview.\n- Set `save` appropriately.\nThis tool has proper capability declaration and is heavily recommended in the security posture of the project.",
3761
3809
  permission: "confirm",
3810
+ // WS-046: gives permission decisions something to key on.
3811
+ // What gets installed is the subject — installing a package runs its
3812
+ // lifecycle scripts, so `express` and `evil-pkg` must not be interchangeable.
3813
+ subjectKey: "packages",
3762
3814
  mutating: true,
3763
3815
  riskTier: "standard",
3764
3816
  icon: "package",
@@ -3928,10 +3980,10 @@ function resolveManifestPath(cwd, pkgManager) {
3928
3980
  case "pnpm":
3929
3981
  case "yarn":
3930
3982
  case "npm":
3931
- return join7(cwd, "package.json");
3983
+ return join8(cwd, "package.json");
3932
3984
  /* v8 ignore next 2 -- pkgManager is always pnpm/yarn/npm; the default is defensive. */
3933
3985
  default:
3934
- return join7(cwd, "package.json");
3986
+ return join8(cwd, "package.json");
3935
3987
  }
3936
3988
  }
3937
3989
  export {
package/dist/json.js CHANGED
@@ -27,12 +27,13 @@ function ensureInsideRoot(absPath, ctx) {
27
27
  function safeResolve(input, ctx) {
28
28
  return ensureInsideRoot(resolvePath(input, ctx), ctx);
29
29
  }
30
- async function assertRealInsideRoot(absPath, ctx) {
31
- if (ctx.allowOutsideProjectRoot) return;
30
+ async function resolveRealInsideRoot(absPath, ctx) {
31
+ if (ctx.allowOutsideProjectRoot) return absPath;
32
32
  const realRoots = await Promise.all(
33
33
  allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r)))
34
34
  );
35
35
  let probe = absPath;
36
+ const pendingTail = [];
36
37
  for (; ; ) {
37
38
  let real;
38
39
  try {
@@ -40,13 +41,16 @@ async function assertRealInsideRoot(absPath, ctx) {
40
41
  } catch (err) {
41
42
  if (err.code === "ENOENT") {
42
43
  const parent = path.dirname(probe);
43
- if (parent === probe) return;
44
+ if (parent === probe) return absPath;
45
+ pendingTail.unshift(path.basename(probe));
44
46
  probe = parent;
45
47
  continue;
46
48
  }
47
49
  throw err;
48
50
  }
49
- if (isInsideAny(real, realRoots)) return;
51
+ if (isInsideAny(real, realRoots)) {
52
+ return pendingTail.length > 0 ? path.join(real, ...pendingTail) : real;
53
+ }
50
54
  throw new Error(
51
55
  `Path "${absPath}" resolves through a symlink outside project root "${realRoots[0]}"`
52
56
  );
@@ -54,11 +58,28 @@ async function assertRealInsideRoot(absPath, ctx) {
54
58
  }
55
59
  async function safeResolveReal(input, ctx) {
56
60
  const abs = safeResolve(input, ctx);
57
- await assertRealInsideRoot(abs, ctx);
58
- return abs;
61
+ return await resolveRealInsideRoot(abs, ctx);
59
62
  }
60
63
 
61
64
  // src/json.ts
65
+ var MAX_JSON_FILE_BYTES = 16 * 1024 * 1024;
66
+ var MAX_JSON_FILE_BYTES_HUMAN = "16 MiB";
67
+ var JsonFileTooLargeError = class extends Error {
68
+ constructor(filePath, size) {
69
+ super(
70
+ `json: "${filePath}" is ${size} bytes \u2014 exceeds the ${MAX_JSON_FILE_BYTES_HUMAN} file-size limit. Extract the relevant portion into a smaller file first, or use a shell tool to query it directly.`
71
+ );
72
+ this.name = "JsonFileTooLargeError";
73
+ }
74
+ };
75
+ async function readJsonFileBounded(filePath, ctx) {
76
+ const resolved = await safeResolveReal(filePath, ctx);
77
+ const stat2 = await fs.stat(resolved);
78
+ if (stat2.size > MAX_JSON_FILE_BYTES) {
79
+ throw new JsonFileTooLargeError(filePath, stat2.size);
80
+ }
81
+ return fs.readFile(resolved, "utf8");
82
+ }
62
83
  var jsonTool = {
63
84
  name: "json",
64
85
  category: "Data",
@@ -66,6 +87,7 @@ var jsonTool = {
66
87
  usageHint: 'VERY USEFUL FOR DATA INSPECTION:\n\n- `action: "parse"` (default): read/pretty-print/convert JSON, JSON5, or YAML from `file` or `data`.\n- `action: "query"`: JMESPath-like query (`a.b[0].c`, `items[*].name`, filters, functions).\n- `action: "validate"`: validate data against a JSON Schema (`schema` param).\n- `action: "transform"`: chain multiple JMESPath transforms (`transforms` param).\n- `action: "merge"`: deep merge `base` and `patch` objects (`conflictResolution` param).\nPrefer this over raw `read` + manual parsing when dealing with configuration or data files.',
67
88
  permission: "auto",
68
89
  mutating: false,
90
+ maxOutputBytes: 262144,
69
91
  timeoutMs: 5e3,
70
92
  capabilities: ["fs.read"],
71
93
  icon: "json",
@@ -132,9 +154,15 @@ async function executeParse(input, ctx) {
132
154
  let raw;
133
155
  if (input.file) {
134
156
  try {
135
- raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
136
- } catch {
137
- return { data: null, formatted: "", type: "unknown", action: "parse", error: "Could not read file" };
157
+ raw = await readJsonFileBounded(input.file, ctx);
158
+ } catch (error) {
159
+ return {
160
+ data: null,
161
+ formatted: "",
162
+ type: "unknown",
163
+ action: "parse",
164
+ error: error instanceof JsonFileTooLargeError ? error.message : "Could not read file"
165
+ };
138
166
  }
139
167
  } else if (input.data) {
140
168
  raw = input.data;
@@ -190,10 +218,16 @@ async function executeQuery(input, ctx) {
190
218
  let parsed;
191
219
  if (input.file) {
192
220
  try {
193
- const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
221
+ const raw = await readJsonFileBounded(input.file, ctx);
194
222
  parsed = JSON.parse(raw);
195
- } catch {
196
- return { data: null, formatted: "", type: "unknown", action: "query", error: "Could not read/parse file" };
223
+ } catch (error) {
224
+ return {
225
+ data: null,
226
+ formatted: "",
227
+ type: "unknown",
228
+ action: "query",
229
+ error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
230
+ };
197
231
  }
198
232
  } else if (input.data) {
199
233
  try {
@@ -232,10 +266,16 @@ async function executeValidate(input, ctx) {
232
266
  let parsed;
233
267
  if (input.file) {
234
268
  try {
235
- const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
269
+ const raw = await readJsonFileBounded(input.file, ctx);
236
270
  parsed = JSON.parse(raw);
237
- } catch {
238
- return { data: null, formatted: "", type: "unknown", action: "validate", error: "Could not read/parse file" };
271
+ } catch (error) {
272
+ return {
273
+ data: null,
274
+ formatted: "",
275
+ type: "unknown",
276
+ action: "validate",
277
+ error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
278
+ };
239
279
  }
240
280
  } else if (input.data) {
241
281
  try {
@@ -274,10 +314,16 @@ async function executeTransform(input, ctx) {
274
314
  let parsed;
275
315
  if (input.file) {
276
316
  try {
277
- const raw = await fs.readFile(await safeResolveReal(input.file, ctx), "utf8");
317
+ const raw = await readJsonFileBounded(input.file, ctx);
278
318
  parsed = JSON.parse(raw);
279
- } catch {
280
- return { data: null, formatted: "", type: "unknown", action: "transform", error: "Could not read/parse file" };
319
+ } catch (error) {
320
+ return {
321
+ data: null,
322
+ formatted: "",
323
+ type: "unknown",
324
+ action: "transform",
325
+ error: error instanceof JsonFileTooLargeError ? error.message : "Could not read/parse file"
326
+ };
281
327
  }
282
328
  } else if (input.data) {
283
329
  try {
@@ -8,8 +8,17 @@ export declare function boardCreateInput(input: KanbanToolInput, title: string):
8
8
  mode: "assess" | "enforce" | "off";
9
9
  decomposition: "auto" | "propose";
10
10
  };
11
- completionGate?: {
12
- enforcement: import("@wrongstack/kanban").KanbanCompletionGateEnforcement;
11
+ } | {
12
+ completionGate: {
13
+ enforcement: 'strict' | 'soft';
14
+ };
15
+ title: string;
16
+ description?: string;
17
+ tags?: string[];
18
+ generatedBy?: string;
19
+ atomicity?: {
20
+ mode: "assess" | "enforce" | "off";
21
+ decomposition: "auto" | "propose";
13
22
  };
14
23
  };
15
24
  export declare function boardUpdatePatch(input: KanbanToolInput): {
@@ -20,8 +29,16 @@ export declare function boardUpdatePatch(input: KanbanToolInput): {
20
29
  mode: "assess" | "enforce" | "off";
21
30
  decomposition: "auto" | "propose";
22
31
  };
23
- completionGate?: {
24
- enforcement: import("@wrongstack/kanban").KanbanCompletionGateEnforcement;
32
+ } | {
33
+ completionGate: {
34
+ enforcement: 'strict' | 'soft';
35
+ };
36
+ title?: string;
37
+ description?: string;
38
+ tags?: string[];
39
+ atomicity?: {
40
+ mode: "assess" | "enforce" | "off";
41
+ decomposition: "auto" | "propose";
25
42
  };
26
43
  };
27
44
  export declare function duplicateBoardOptions(input: KanbanToolInput): {
package/dist/kanban.js CHANGED
@@ -345,6 +345,10 @@ async function handleKanbanDetailAction(projectRoot, input) {
345
345
  }
346
346
 
347
347
  // src/kanban-board-inputs.ts
348
+ function agentSettableGate(enforcement) {
349
+ if (enforcement === void 0 || enforcement === "off") return {};
350
+ return { completionGate: { enforcement } };
351
+ }
348
352
  function boardCreateInput(input, title) {
349
353
  return {
350
354
  title,
@@ -357,7 +361,7 @@ function boardCreateInput(input, title) {
357
361
  decomposition: input.atomicityDecomposition ?? "propose"
358
362
  }
359
363
  } : {},
360
- ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
364
+ ...agentSettableGate(input.gateEnforcement)
361
365
  };
362
366
  }
363
367
  function boardUpdatePatch(input) {
@@ -371,7 +375,7 @@ function boardUpdatePatch(input) {
371
375
  decomposition: input.atomicityDecomposition ?? "propose"
372
376
  }
373
377
  } : {},
374
- ...input.gateEnforcement !== void 0 ? { completionGate: { enforcement: input.gateEnforcement } } : {}
378
+ ...agentSettableGate(input.gateEnforcement)
375
379
  };
376
380
  }
377
381
  function duplicateBoardOptions(input) {
@@ -386,6 +390,7 @@ function duplicateBoardOptions(input) {
386
390
 
387
391
  // src/kanban-task-inputs.ts
388
392
  import { randomUUID } from "node:crypto";
393
+ import { clampSubagentCapabilities } from "@wrongstack/core/security";
389
394
  function taskInput(input) {
390
395
  const assignment = hasAssignmentInput(input) ? assignmentForTaskCreate(input) : void 0;
391
396
  return {
@@ -483,6 +488,10 @@ function taskPatch(input) {
483
488
  ...input.actualHours !== void 0 ? { actualHours: input.actualHours } : {}
484
489
  };
485
490
  }
491
+ function clampRequestedCapabilities(requested) {
492
+ if (requested === void 0) return void 0;
493
+ return clampSubagentCapabilities(requested).granted;
494
+ }
486
495
  function assignmentInput(input) {
487
496
  return {
488
497
  agentId: input.agentId,
@@ -493,7 +502,7 @@ function assignmentInput(input) {
493
502
  fallbackProfile: input.fallbackProfile,
494
503
  fallbackModels: input.fallbackModels,
495
504
  tools: input.tools,
496
- allowedCapabilities: input.allowedCapabilities,
505
+ allowedCapabilities: clampRequestedCapabilities(input.allowedCapabilities),
497
506
  assignee: input.assignee,
498
507
  leaseId: input.leaseId,
499
508
  claimedAt: input.claimedAt,
@@ -520,7 +529,7 @@ function assignmentForTaskCreate(input) {
520
529
  ...input.fallbackProfile !== void 0 ? { fallbackProfile: input.fallbackProfile } : {},
521
530
  ...input.fallbackModels !== void 0 ? { fallbackModels: input.fallbackModels } : {},
522
531
  ...input.tools !== void 0 ? { tools: input.tools } : {},
523
- ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: input.allowedCapabilities } : {},
532
+ ...input.allowedCapabilities !== void 0 ? { allowedCapabilities: clampRequestedCapabilities(input.allowedCapabilities) } : {},
524
533
  ...input.leaseId !== void 0 ? { leaseId: input.leaseId } : {},
525
534
  ...input.claimedAt !== void 0 ? { claimedAt: input.claimedAt } : {},
526
535
  ...input.heartbeatAt !== void 0 ? { heartbeatAt: input.heartbeatAt } : {},
@@ -731,7 +740,13 @@ var KANBAN_INPUT_SCHEMA = {
731
740
  moveTasksToColumnId: { type: "string" },
732
741
  atomicityMode: { type: "string", enum: ["off", "assess", "enforce"] },
733
742
  atomicityDecomposition: { type: "string", enum: ["auto", "propose"] },
734
- gateEnforcement: { type: "string", enum: ["strict", "soft", "off"] },
743
+ gateEnforcement: {
744
+ type: "string",
745
+ // WS-023: `'off'` is deliberately absent. The agent whose work this gate
746
+ // checks must not be able to switch it off; it may only tighten. Turning
747
+ // a gate off stays a human decision, made through board config.
748
+ enum: ["strict", "soft"]
749
+ },
735
750
  subtasks: {
736
751
  type: "array",
737
752
  minItems: 2,