@wrongstack/tools 0.296.4 → 0.298.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/_spawn-stream.d.ts +2 -0
  2. package/dist/_util.d.ts +29 -1
  3. package/dist/audit.js +128 -65
  4. package/dist/bash.js +20 -4
  5. package/dist/batch-tool-use.js +70 -7
  6. package/dist/browser/index.js +31 -1
  7. package/dist/builtin.js +883 -351
  8. package/dist/codebase-index/index.js +95 -16
  9. package/dist/codebase-index/project-server-protocol.d.ts +26 -0
  10. package/dist/codebase-index/project-server.js +76 -16
  11. package/dist/codebase-index/worker.js +36 -13
  12. package/dist/codebase-index/writer-search-helpers.d.ts +16 -0
  13. package/dist/codebase-index/writer.d.ts +13 -1
  14. package/dist/design.d.ts +1 -1
  15. package/dist/design.js +4 -3
  16. package/dist/diff.js +11 -1
  17. package/dist/e2e.js +9 -6
  18. package/dist/edit.js +9 -6
  19. package/dist/exec.js +562 -516
  20. package/dist/format.js +131 -65
  21. package/dist/git.js +22 -2
  22. package/dist/glob.js +11 -5
  23. package/dist/grep.js +160 -104
  24. package/dist/index.js +834 -290
  25. package/dist/install.js +135 -68
  26. package/dist/json.js +64 -18
  27. package/dist/kanban-board-inputs.d.ts +21 -4
  28. package/dist/kanban.js +20 -5
  29. package/dist/languages/index.js +143 -71
  30. package/dist/lint.js +128 -65
  31. package/dist/next-steps.d.ts +23 -0
  32. package/dist/next-steps.js +4 -0
  33. package/dist/outdated.js +101 -26
  34. package/dist/pack.js +883 -351
  35. package/dist/patch.js +11 -1
  36. package/dist/plan.js +32 -10
  37. package/dist/process-registry.d.ts +9 -0
  38. package/dist/process-registry.js +15 -0
  39. package/dist/ps-slash.js +15 -0
  40. package/dist/read.js +104 -23
  41. package/dist/replace.js +11 -3
  42. package/dist/scaffold.js +9 -6
  43. package/dist/session-kanban.js +21 -5
  44. package/dist/task.js +3 -1
  45. package/dist/test.js +128 -65
  46. package/dist/todo.js +3 -1
  47. package/dist/tool-tier.js +883 -351
  48. package/dist/tool-use.d.ts +1 -1
  49. package/dist/tool-use.js +28 -4
  50. package/dist/tree.d.ts +1 -0
  51. package/dist/tree.js +44 -8
  52. package/dist/typecheck.js +128 -65
  53. package/dist/write.js +9 -6
  54. package/package.json +5 -4
  55. package/dist/_concurrency.d.ts.map +0 -1
  56. package/dist/_danger-detect.d.ts.map +0 -1
  57. package/dist/_edit-match.d.ts.map +0 -1
  58. package/dist/_env.d.ts.map +0 -1
  59. package/dist/_fetch-guard.d.ts.map +0 -1
  60. package/dist/_output-spool.d.ts.map +0 -1
  61. package/dist/_redact-command.d.ts.map +0 -1
  62. package/dist/_regex.d.ts.map +0 -1
  63. package/dist/_session-shell.d.ts.map +0 -1
  64. package/dist/_shell-pick.d.ts.map +0 -1
  65. package/dist/_spawn-stream.d.ts.map +0 -1
  66. package/dist/_syntax-check.d.ts.map +0 -1
  67. package/dist/_util.d.ts.map +0 -1
  68. package/dist/_win32-resolve.d.ts.map +0 -1
  69. package/dist/audit.d.ts.map +0 -1
  70. package/dist/audit.js.map +0 -7
  71. package/dist/auto-proceed-loop-guard.d.ts.map +0 -1
  72. package/dist/auto-proceed-loop-guard.js.map +0 -7
  73. package/dist/bash-kill-guard.d.ts.map +0 -1
  74. package/dist/bash.d.ts.map +0 -1
  75. package/dist/bash.js.map +0 -7
  76. package/dist/batch-tool-use.d.ts.map +0 -1
  77. package/dist/batch-tool-use.js.map +0 -7
  78. package/dist/browser/artifacts.d.ts.map +0 -1
  79. package/dist/browser/index.d.ts.map +0 -1
  80. package/dist/browser/index.js.map +0 -7
  81. package/dist/browser/manager.d.ts.map +0 -1
  82. package/dist/browser/network-guard-proxy.d.ts.map +0 -1
  83. package/dist/browser/security.d.ts.map +0 -1
  84. package/dist/browser/tools.d.ts.map +0 -1
  85. package/dist/browser/types.d.ts.map +0 -1
  86. package/dist/builtin.d.ts.map +0 -1
  87. package/dist/builtin.js.map +0 -7
  88. package/dist/circuit-breaker.d.ts.map +0 -1
  89. package/dist/circuit-breaker.js.map +0 -7
  90. package/dist/codebase-index/background-indexer.d.ts.map +0 -1
  91. package/dist/codebase-index/bm25.d.ts.map +0 -1
  92. package/dist/codebase-index/circuit-breaker.d.ts.map +0 -1
  93. package/dist/codebase-index/codebase-index-tool.d.ts.map +0 -1
  94. package/dist/codebase-index/codebase-search-tool.d.ts.map +0 -1
  95. package/dist/codebase-index/codebase-stats-tool.d.ts.map +0 -1
  96. package/dist/codebase-index/dead-code-scan.d.ts.map +0 -1
  97. package/dist/codebase-index/generic-parser.d.ts.map +0 -1
  98. package/dist/codebase-index/gitignore.d.ts.map +0 -1
  99. package/dist/codebase-index/go-parser.d.ts.map +0 -1
  100. package/dist/codebase-index/index-service.d.ts.map +0 -1
  101. package/dist/codebase-index/index.d.ts.map +0 -1
  102. package/dist/codebase-index/index.js.map +0 -7
  103. package/dist/codebase-index/indexer.d.ts.map +0 -1
  104. package/dist/codebase-index/json-parser.d.ts.map +0 -1
  105. package/dist/codebase-index/languages.d.ts.map +0 -1
  106. package/dist/codebase-index/lsp-kind.d.ts.map +0 -1
  107. package/dist/codebase-index/parser-dispatch.d.ts.map +0 -1
  108. package/dist/codebase-index/project-server-cache.d.ts.map +0 -1
  109. package/dist/codebase-index/project-server-client.d.ts.map +0 -1
  110. package/dist/codebase-index/project-server-endpoint.d.ts.map +0 -1
  111. package/dist/codebase-index/project-server-protocol.d.ts.map +0 -1
  112. package/dist/codebase-index/project-server.d.ts.map +0 -1
  113. package/dist/codebase-index/project-server.js.map +0 -7
  114. package/dist/codebase-index/py-parser.d.ts.map +0 -1
  115. package/dist/codebase-index/refs-extractor.d.ts.map +0 -1
  116. package/dist/codebase-index/rs-parser.d.ts.map +0 -1
  117. package/dist/codebase-index/schema.d.ts.map +0 -1
  118. package/dist/codebase-index/spawn-gate.d.ts.map +0 -1
  119. package/dist/codebase-index/sqlite-runtime.d.ts.map +0 -1
  120. package/dist/codebase-index/ts-parser.d.ts.map +0 -1
  121. package/dist/codebase-index/worker-protocol.d.ts.map +0 -1
  122. package/dist/codebase-index/worker.d.ts.map +0 -1
  123. package/dist/codebase-index/worker.js.map +0 -7
  124. package/dist/codebase-index/writer-admin.d.ts.map +0 -1
  125. package/dist/codebase-index/writer-bulk-insert.d.ts.map +0 -1
  126. package/dist/codebase-index/writer-graph-helpers.d.ts.map +0 -1
  127. package/dist/codebase-index/writer-graph-reader.d.ts.map +0 -1
  128. package/dist/codebase-index/writer-helpers.d.ts.map +0 -1
  129. package/dist/codebase-index/writer-pragmas.d.ts.map +0 -1
  130. package/dist/codebase-index/writer-ref-mapper.d.ts.map +0 -1
  131. package/dist/codebase-index/writer-schema.d.ts.map +0 -1
  132. package/dist/codebase-index/writer-search-helpers.d.ts.map +0 -1
  133. package/dist/codebase-index/writer-store-pool.d.ts.map +0 -1
  134. package/dist/codebase-index/writer.d.ts.map +0 -1
  135. package/dist/codebase-index/yaml-parser.d.ts.map +0 -1
  136. package/dist/design.d.ts.map +0 -1
  137. package/dist/design.js.map +0 -7
  138. package/dist/diff.d.ts.map +0 -1
  139. package/dist/diff.js.map +0 -7
  140. package/dist/document.d.ts.map +0 -1
  141. package/dist/document.js.map +0 -7
  142. package/dist/e2e.d.ts.map +0 -1
  143. package/dist/e2e.js.map +0 -7
  144. package/dist/edit.d.ts.map +0 -1
  145. package/dist/edit.js.map +0 -7
  146. package/dist/exec-kill-guard.d.ts.map +0 -1
  147. package/dist/exec.d.ts.map +0 -1
  148. package/dist/exec.js.map +0 -7
  149. package/dist/fetch.d.ts.map +0 -1
  150. package/dist/fetch.js.map +0 -7
  151. package/dist/format.d.ts.map +0 -1
  152. package/dist/format.js.map +0 -7
  153. package/dist/git.d.ts.map +0 -1
  154. package/dist/git.js.map +0 -7
  155. package/dist/glob.d.ts.map +0 -1
  156. package/dist/glob.js.map +0 -7
  157. package/dist/grep.d.ts.map +0 -1
  158. package/dist/grep.js.map +0 -7
  159. package/dist/index.d.ts.map +0 -1
  160. package/dist/index.js.map +0 -7
  161. package/dist/install.d.ts.map +0 -1
  162. package/dist/install.js.map +0 -7
  163. package/dist/json.d.ts.map +0 -1
  164. package/dist/json.js.map +0 -7
  165. package/dist/kanban-board-inputs.d.ts.map +0 -1
  166. package/dist/kanban-decomposition-actions.d.ts.map +0 -1
  167. package/dist/kanban-detail-actions.d.ts.map +0 -1
  168. package/dist/kanban-evidence-bridge.d.ts.map +0 -1
  169. package/dist/kanban-presence.d.ts.map +0 -1
  170. package/dist/kanban-split-task-handler.d.ts.map +0 -1
  171. package/dist/kanban-task-inputs.d.ts.map +0 -1
  172. package/dist/kanban-tool-results.d.ts.map +0 -1
  173. package/dist/kanban-tool-schema.d.ts.map +0 -1
  174. package/dist/kanban-tool-types.d.ts.map +0 -1
  175. package/dist/kanban.d.ts.map +0 -1
  176. package/dist/kanban.js.map +0 -7
  177. package/dist/languages/detect.d.ts.map +0 -1
  178. package/dist/languages/diagnostics.d.ts.map +0 -1
  179. package/dist/languages/execute-tool.d.ts.map +0 -1
  180. package/dist/languages/execute.d.ts.map +0 -1
  181. package/dist/languages/index.d.ts.map +0 -1
  182. package/dist/languages/index.js.map +0 -7
  183. package/dist/languages/legacy-bridge.d.ts.map +0 -1
  184. package/dist/languages/package-tool.d.ts.map +0 -1
  185. package/dist/languages/plan.d.ts.map +0 -1
  186. package/dist/languages/profile-helpers.d.ts.map +0 -1
  187. package/dist/languages/profiles/additional.d.ts.map +0 -1
  188. package/dist/languages/profiles/primary.d.ts.map +0 -1
  189. package/dist/languages/registry.d.ts.map +0 -1
  190. package/dist/languages/tool.d.ts.map +0 -1
  191. package/dist/languages/types.d.ts.map +0 -1
  192. package/dist/lint.d.ts.map +0 -1
  193. package/dist/lint.js.map +0 -7
  194. package/dist/logs.d.ts.map +0 -1
  195. package/dist/logs.js.map +0 -7
  196. package/dist/memory.d.ts.map +0 -1
  197. package/dist/memory.js.map +0 -7
  198. package/dist/mode.d.ts.map +0 -1
  199. package/dist/mode.js.map +0 -7
  200. package/dist/next-steps.d.ts.map +0 -1
  201. package/dist/next-steps.js.map +0 -7
  202. package/dist/outdated.d.ts.map +0 -1
  203. package/dist/outdated.js.map +0 -7
  204. package/dist/pack.d.ts.map +0 -1
  205. package/dist/pack.js.map +0 -7
  206. package/dist/patch.d.ts.map +0 -1
  207. package/dist/patch.js.map +0 -7
  208. package/dist/plan.d.ts.map +0 -1
  209. package/dist/plan.js.map +0 -7
  210. package/dist/process-guardian.d.ts.map +0 -1
  211. package/dist/process-registry-persistent.d.ts.map +0 -1
  212. package/dist/process-registry.d.ts.map +0 -1
  213. package/dist/process-registry.js.map +0 -7
  214. package/dist/ps-slash.d.ts.map +0 -1
  215. package/dist/ps-slash.js.map +0 -7
  216. package/dist/read.d.ts.map +0 -1
  217. package/dist/read.js.map +0 -7
  218. package/dist/replace.d.ts.map +0 -1
  219. package/dist/replace.js.map +0 -7
  220. package/dist/scaffold.d.ts.map +0 -1
  221. package/dist/scaffold.js.map +0 -7
  222. package/dist/search.d.ts.map +0 -1
  223. package/dist/search.js.map +0 -7
  224. package/dist/session-kanban.d.ts.map +0 -1
  225. package/dist/session-kanban.js.map +0 -7
  226. package/dist/set-working-dir.d.ts.map +0 -1
  227. package/dist/skill.d.ts.map +0 -1
  228. package/dist/skill.js.map +0 -7
  229. package/dist/spawn-background.d.ts.map +0 -1
  230. package/dist/task.d.ts.map +0 -1
  231. package/dist/task.js.map +0 -7
  232. package/dist/test.d.ts.map +0 -1
  233. package/dist/test.js.map +0 -7
  234. package/dist/todo.d.ts.map +0 -1
  235. package/dist/todo.js.map +0 -7
  236. package/dist/tool-diff.d.ts.map +0 -1
  237. package/dist/tool-diff.js.map +0 -7
  238. package/dist/tool-help.d.ts.map +0 -1
  239. package/dist/tool-help.js.map +0 -7
  240. package/dist/tool-icon-map.d.ts.map +0 -1
  241. package/dist/tool-icons.d.ts.map +0 -1
  242. package/dist/tool-icons.js.map +0 -7
  243. package/dist/tool-search.d.ts.map +0 -1
  244. package/dist/tool-search.js.map +0 -7
  245. package/dist/tool-summary.d.ts.map +0 -1
  246. package/dist/tool-summary.js.map +0 -7
  247. package/dist/tool-tier.d.ts.map +0 -1
  248. package/dist/tool-tier.js.map +0 -7
  249. package/dist/tool-use.d.ts.map +0 -1
  250. package/dist/tool-use.js.map +0 -7
  251. package/dist/tree.d.ts.map +0 -1
  252. package/dist/tree.js.map +0 -7
  253. package/dist/typecheck.d.ts.map +0 -1
  254. package/dist/typecheck.js.map +0 -7
  255. package/dist/win32.d.ts.map +0 -1
  256. package/dist/win32.js.map +0 -7
  257. package/dist/write.d.ts.map +0 -1
  258. package/dist/write.js.map +0 -7
package/dist/test.js CHANGED
@@ -81,11 +81,12 @@ function createOutputSpool(opts) {
81
81
  return {
82
82
  write(text) {
83
83
  if (finalized || !text) return;
84
- totalBytes += Buffer.byteLength(text, "utf8");
84
+ const textBytes = Buffer.byteLength(text, "utf8");
85
+ totalBytes += textBytes;
85
86
  if (!stream && !failed) {
86
- if (headBytes + text.length <= threshold) {
87
+ if (headBytes + textBytes <= threshold) {
87
88
  head += text;
88
- headBytes += text.length;
89
+ headBytes += textBytes;
89
90
  return;
90
91
  }
91
92
  head += text;
@@ -95,7 +96,7 @@ function createOutputSpool(opts) {
95
96
  }
96
97
  if (stream) {
97
98
  if (stream.writableLength > SPOOL_WRITE_HWM_BYTES) {
98
- droppedBytes += Buffer.byteLength(text, "utf8");
99
+ droppedBytes += textBytes;
99
100
  return;
100
101
  }
101
102
  stream.write(text);
@@ -117,6 +118,52 @@ function createOutputSpool(opts) {
117
118
  };
118
119
  }
119
120
 
121
+ // src/_win32-resolve.ts
122
+ import * as fs from "node:fs";
123
+ import * as path2 from "node:path";
124
+ function resolveWin32Command(cmd) {
125
+ if (process.platform !== "win32") return cmd;
126
+ if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
127
+ return cmd;
128
+ }
129
+ const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
130
+ const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
131
+ for (const dir of pathDirs) {
132
+ const base = path2.join(dir, cmd);
133
+ for (const ext of pathext) {
134
+ const full = `${base}${ext}`;
135
+ try {
136
+ fs.accessSync(full, fs.constants.X_OK);
137
+ return full;
138
+ } catch {
139
+ }
140
+ }
141
+ }
142
+ return cmd;
143
+ }
144
+ var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
145
+ function assertSafeWin32ShellArgs(args) {
146
+ for (const arg of args) {
147
+ if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
148
+ throw new Error(
149
+ '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)
150
+ );
151
+ }
152
+ }
153
+ }
154
+ function buildWin32CmdShimInvocation(command, args = []) {
155
+ assertSafeWin32ShellArgs([command, ...args]);
156
+ const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
157
+ return {
158
+ command: process.env["COMSPEC"] ?? "cmd.exe",
159
+ args: ["/d", "/c", line],
160
+ windowsVerbatimArguments: true
161
+ };
162
+ }
163
+ function quoteWin32CmdArg(arg) {
164
+ return `"${arg}"`;
165
+ }
166
+
120
167
  // src/process-registry.ts
121
168
  import { spawn } from "node:child_process";
122
169
  import * as os from "node:os";
@@ -448,6 +495,7 @@ var ProcessRegistryImpl = class {
448
495
  }
449
496
  /** Get all tracked processes. */
450
497
  list() {
498
+ this._pruneAllStale();
451
499
  return Array.from(this.processes.values());
452
500
  }
453
501
  /** Get processes filtered by name (e.g. 'bash', 'exec'). */
@@ -478,6 +526,7 @@ var ProcessRegistryImpl = class {
478
526
  * Combined stats for observability — used by /ps and the TUI status bar.
479
527
  */
480
528
  stats() {
529
+ this._pruneAllStale();
481
530
  return {
482
531
  activeCount: this.activeCount,
483
532
  backgroundCount: this.activeBackgroundCount,
@@ -717,6 +766,19 @@ var ProcessRegistryImpl = class {
717
766
  this.processes.delete(pid);
718
767
  }
719
768
  }
769
+ /**
770
+ * Remove every stale entry, not just one PID. `list()`/`stats()` — the
771
+ * surfaces the TUI status bar and `/ps` poll — must prune too: a child
772
+ * whose 'close' event never fires (e.g. Windows grandchildren holding stdio
773
+ * open) would otherwise linger in the registry until someone looks up its
774
+ * exact PID, and PID reuse meanwhile makes `get()`/`kill()` target the
775
+ * wrong process. RAM-leak audit 2026-07-31, MEDIUM.
776
+ */
777
+ _pruneAllStale() {
778
+ for (const [pid, entry] of this.processes) {
779
+ if (this._isStaleEntry(entry)) this.processes.delete(pid);
780
+ }
781
+ }
720
782
  };
721
783
  var _registry;
722
784
  function getProcessRegistry() {
@@ -726,60 +788,17 @@ function getProcessRegistry() {
726
788
  return _registry;
727
789
  }
728
790
 
729
- // src/_win32-resolve.ts
730
- import * as fs from "node:fs";
731
- import * as path2 from "node:path";
732
- function resolveWin32Command(cmd) {
733
- if (process.platform !== "win32") return cmd;
734
- if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
735
- return cmd;
736
- }
737
- const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
738
- const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
739
- for (const dir of pathDirs) {
740
- const base = path2.join(dir, cmd);
741
- for (const ext of pathext) {
742
- const full = `${base}${ext}`;
743
- try {
744
- fs.accessSync(full, fs.constants.X_OK);
745
- return full;
746
- } catch {
747
- }
748
- }
749
- }
750
- return cmd;
751
- }
752
- var WIN32_SHELL_META = /[&|<>"\r\n\0]/;
753
- function assertSafeWin32ShellArgs(args) {
754
- for (const arg of args) {
755
- if (typeof arg === "string" && WIN32_SHELL_META.test(arg)) {
756
- throw new Error(
757
- '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)
758
- );
759
- }
760
- }
761
- }
762
- function buildWin32CmdShimInvocation(command, args = []) {
763
- assertSafeWin32ShellArgs([command, ...args]);
764
- const line = ["call", quoteWin32CmdArg(command), ...args.map(quoteWin32CmdArg)].join(" ");
765
- return {
766
- command: process.env["COMSPEC"] ?? "cmd.exe",
767
- args: ["/d", "/c", line],
768
- windowsVerbatimArguments: true
769
- };
770
- }
771
- function quoteWin32CmdArg(arg) {
772
- return `"${arg}"`;
773
- }
774
-
775
791
  // src/_spawn-stream.ts
776
792
  var isWin = process.platform === "win32";
777
793
  async function* spawnStream(opts) {
778
794
  const max = opts.maxBytes ?? 2e5;
779
795
  const flushAt = opts.flushBytes ?? 4 * 1024;
780
796
  const maxQueue = opts.maxQueueSize ?? 500;
797
+ const maxQueueBytes = opts.maxQueueBytes ?? 1024 * 1024;
781
798
  let stdout = "";
782
799
  let stderr = "";
800
+ let stdoutRetainedBytes = 0;
801
+ let stderrRetainedBytes = 0;
783
802
  let pending = "";
784
803
  let error;
785
804
  const spool = createOutputSpool({ tool: opts.cmd, thresholdBytes: max });
@@ -821,6 +840,7 @@ async function* spawnStream(opts) {
821
840
  });
822
841
  }
823
842
  const queue = [];
843
+ let queuedBytes = 0;
824
844
  let waiter;
825
845
  let paused = false;
826
846
  const wake = () => {
@@ -831,7 +851,7 @@ async function* spawnStream(opts) {
831
851
  }
832
852
  };
833
853
  const resume = () => {
834
- if (paused && queue.length < maxQueue) {
854
+ if (paused && queue.length <= Math.floor(maxQueue / 2) && queuedBytes <= Math.floor(maxQueueBytes / 2)) {
835
855
  paused = false;
836
856
  child.stdout?.resume();
837
857
  child.stderr?.resume();
@@ -841,11 +861,16 @@ async function* spawnStream(opts) {
841
861
  const s = c.toString();
842
862
  stdoutBytes += c.byteLength;
843
863
  emitProcessOutput({ pid, stream: "stdout", chunk: c });
844
- if (stdout.length < max) stdout += s;
864
+ if (stdoutRetainedBytes < max) {
865
+ const retained = utf8Prefix(s, max - stdoutRetainedBytes);
866
+ stdout += retained;
867
+ stdoutRetainedBytes += Buffer.byteLength(retained, "utf8");
868
+ }
845
869
  spool.write(s);
846
- queue.push({ kind: "out", data: s });
870
+ queue.push({ kind: "out", data: s, bytes: c.byteLength });
871
+ queuedBytes += c.byteLength;
847
872
  wake();
848
- if (!paused && queue.length >= maxQueue) {
873
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
849
874
  paused = true;
850
875
  child.stdout?.pause();
851
876
  child.stderr?.pause();
@@ -855,11 +880,16 @@ async function* spawnStream(opts) {
855
880
  const s = c.toString();
856
881
  stderrBytes += c.byteLength;
857
882
  emitProcessOutput({ pid, stream: "stderr", chunk: c });
858
- if (stderr.length < max) stderr += s;
883
+ if (stderrRetainedBytes < max) {
884
+ const retained = utf8Prefix(s, max - stderrRetainedBytes);
885
+ stderr += retained;
886
+ stderrRetainedBytes += Buffer.byteLength(retained, "utf8");
887
+ }
859
888
  spool.write(s);
860
- queue.push({ kind: "err", data: s });
889
+ queue.push({ kind: "err", data: s, bytes: c.byteLength });
890
+ queuedBytes += c.byteLength;
861
891
  wake();
862
- if (!paused && queue.length >= maxQueue) {
892
+ if (!paused && (queue.length >= maxQueue || queuedBytes >= maxQueueBytes)) {
863
893
  paused = true;
864
894
  child.stdout?.pause();
865
895
  child.stderr?.pause();
@@ -869,7 +899,9 @@ async function* spawnStream(opts) {
869
899
  child.stderr?.on("data", onErr);
870
900
  child.on("error", (e) => {
871
901
  error = e.message;
872
- queue.push({ kind: "error", data: e.message });
902
+ const bytes = Buffer.byteLength(e.message, "utf8");
903
+ queue.push({ kind: "error", data: e.message, bytes });
904
+ queuedBytes += bytes;
873
905
  wake();
874
906
  });
875
907
  const completeTelemetry = (code, signal, timedOut = false) => {
@@ -890,7 +922,13 @@ async function* spawnStream(opts) {
890
922
  if (typeof pid === "number") registry.unregister(pid);
891
923
  const exitCode2 = code ?? (signal ? 1 : 0);
892
924
  completeTelemetry(exitCode2, signal ?? void 0);
893
- queue.push({ kind: "close", data: "", code: exitCode2, ...signal ? { signal } : {} });
925
+ queue.push({
926
+ kind: "close",
927
+ data: "",
928
+ bytes: 0,
929
+ code: exitCode2,
930
+ ...signal ? { signal } : {}
931
+ });
894
932
  wake();
895
933
  });
896
934
  const onAbort = () => {
@@ -902,7 +940,7 @@ async function* spawnStream(opts) {
902
940
  } catch {
903
941
  }
904
942
  }
905
- queue.push({ kind: "close", data: "", code: 124 });
943
+ queue.push({ kind: "close", data: "", bytes: 0, code: 124 });
906
944
  completeTelemetry(124, "SIGKILL", true);
907
945
  wake();
908
946
  };
@@ -920,6 +958,7 @@ async function* spawnStream(opts) {
920
958
  });
921
959
  }
922
960
  const chunk = queue.shift();
961
+ queuedBytes -= chunk.bytes;
923
962
  resume();
924
963
  if (chunk.kind === "close") {
925
964
  if (!spawnFailed) exitCode = chunk.code ?? 0;
@@ -946,7 +985,7 @@ async function* spawnStream(opts) {
946
985
  stdout: spooled ? stdout + spoolNote(spooled) : stdout,
947
986
  stderr,
948
987
  exitCode,
949
- truncated: stdout.length >= max || stderr.length >= max,
988
+ truncated: stdoutBytes > stdoutRetainedBytes || stderrBytes > stderrRetainedBytes,
950
989
  error,
951
990
  spoolPath: spooled?.path,
952
991
  spoolBytes: spooled?.bytes
@@ -970,6 +1009,20 @@ async function* spawnStream(opts) {
970
1009
  }
971
1010
  }
972
1011
  }
1012
+ function utf8Prefix(text, maxBytes) {
1013
+ if (maxBytes <= 0) return "";
1014
+ if (Buffer.byteLength(text, "utf8") <= maxBytes) return text;
1015
+ let low = 0;
1016
+ let high = text.length;
1017
+ while (low < high) {
1018
+ const mid = Math.ceil((low + high) / 2);
1019
+ if (Buffer.byteLength(text.slice(0, mid), "utf8") <= maxBytes) low = mid;
1020
+ else high = mid - 1;
1021
+ }
1022
+ let end = low;
1023
+ if (end > 0 && /[\uD800-\uDBFF]/.test(text[end - 1])) end--;
1024
+ return text.slice(0, end);
1025
+ }
973
1026
 
974
1027
  // src/_util.ts
975
1028
  import * as path3 from "node:path";
@@ -2537,8 +2590,8 @@ async function scanDirectory(directory, depth, profiles, limits, state, extraIgn
2537
2590
  collectFileEvidence(directory, fullPath, entry.name, profiles, state);
2538
2591
  }
2539
2592
  }
2540
- function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2541
- const lower = basename4.toLowerCase();
2593
+ function collectFileEvidence(directory, fullPath, basename5, profiles, state) {
2594
+ const lower = basename5.toLowerCase();
2542
2595
  const extension = path4.extname(lower);
2543
2596
  for (const profile of profiles) {
2544
2597
  const detector = profile.detectors.find(
@@ -2549,7 +2602,7 @@ function collectFileEvidence(directory, fullPath, basename4, profiles, state) {
2549
2602
  candidate.evidence.push({
2550
2603
  kind: detector.kind,
2551
2604
  path: fullPath,
2552
- value: basename4,
2605
+ value: basename5,
2553
2606
  weight: detector.weight
2554
2607
  });
2555
2608
  if (detector.kind === "manifest" || detector.kind === "config") {
@@ -2969,10 +3022,11 @@ var MAX_ARGUMENTS = 128;
2969
3022
  var MAX_ARGUMENT_LENGTH = 4096;
2970
3023
  var PACKAGE_NAME_RE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+(?:@[a-z0-9*+._~^<>=|-]+)?$/i;
2971
3024
  var COMPOSER_PACKAGE_RE = /^[a-z0-9_.-]+\/[a-z0-9_.-]+(?::[a-z0-9*+._~^<>=|-]+)?$/i;
2972
- var GO_MODULE_RE = /^(?:[a-z0-9.-]+\.)+[a-z0-9.-]+\/[a-z0-9._~+/@-]+$/i;
3025
+ var GO_MODULE_RE = /^(?:[a-z0-9-]+\.)+[a-z0-9-]+\/[a-z0-9._~+/@-]+$/i;
2973
3026
  var PYTHON_PACKAGE_RE = /^[a-z0-9._-]+(?:\[[a-z0-9._,-]+\])?==[a-z0-9*+._!~-]+$/i;
2974
3027
  async function planLanguageOperation(options) {
2975
3028
  validateOperationOptions(options.operation, options.operationOptions?.packages);
3029
+ validateFilterOption(options.operationOptions?.filter);
2976
3030
  const detection = await detectLanguageWorkspaces(options);
2977
3031
  const candidates = detection.workspaces.filter(
2978
3032
  (workspace) => workspace.capabilities.includes(options.operation)
@@ -3145,6 +3199,15 @@ function workspaceDepth(workspaceRoot, projectRoot) {
3145
3199
  const relative5 = path6.relative(path6.resolve(projectRoot), path6.resolve(workspaceRoot));
3146
3200
  return relative5 === "" ? 0 : relative5.split(path6.sep).length;
3147
3201
  }
3202
+ function validateFilterOption(filter) {
3203
+ if (filter === void 0) return;
3204
+ if (filter.startsWith("-") || filter.includes(" --")) {
3205
+ throw new Error(`Invalid test filter (parsed as a runner option): "${filter}"`);
3206
+ }
3207
+ if (filter.length > MAX_ARGUMENT_LENGTH || /[\r\n\0]/.test(filter)) {
3208
+ throw new Error("Invalid test filter (oversized or contains a control character)");
3209
+ }
3210
+ }
3148
3211
  function validateOperationOptions(operation, packages) {
3149
3212
  if (!operation.startsWith("package-") || !packages) return;
3150
3213
  for (const value of packages) {
package/dist/todo.js CHANGED
@@ -68,7 +68,9 @@ async function ensureSessionKanbanBoard(projectRoot, sessionId) {
68
68
  description: "Live session work: todos, tasks, and plan items.",
69
69
  tags: sessionBoardTags(sessionId),
70
70
  columns: SESSION_KANBAN_COLUMNS,
71
- generatedBy: `session-kanban:${sessionId}`
71
+ generatedBy: `session-kanban:${sessionId}`,
72
+ kind: "session_mirror",
73
+ retention: { mode: "archive_after_ttl", ttlMs: 7 * 24 * 60 * 60 * 1e3 }
72
74
  });
73
75
  }
74
76
  if (!sameColumns(board.columns) || !board.tags?.includes(SESSION_BOARD_TAG)) {