@wrongstack/tools 0.284.1 → 0.286.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 (330) hide show
  1. package/README.md +19 -0
  2. package/dist/_concurrency.d.ts +16 -0
  3. package/dist/_concurrency.d.ts.map +1 -0
  4. package/dist/_danger-detect.d.ts +54 -0
  5. package/dist/_danger-detect.d.ts.map +1 -0
  6. package/dist/_edit-match.d.ts +86 -0
  7. package/dist/_edit-match.d.ts.map +1 -0
  8. package/dist/_env.d.ts +6 -0
  9. package/dist/_env.d.ts.map +1 -0
  10. package/dist/_fetch-guard.d.ts +31 -0
  11. package/dist/_fetch-guard.d.ts.map +1 -0
  12. package/dist/_output-spool.d.ts +38 -0
  13. package/dist/_output-spool.d.ts.map +1 -0
  14. package/dist/_redact-command.d.ts +6 -0
  15. package/dist/_redact-command.d.ts.map +1 -0
  16. package/dist/_regex.d.ts +39 -0
  17. package/dist/_regex.d.ts.map +1 -0
  18. package/dist/_session-shell.d.ts +73 -0
  19. package/dist/_session-shell.d.ts.map +1 -0
  20. package/dist/_shell-pick.d.ts +158 -0
  21. package/dist/_shell-pick.d.ts.map +1 -0
  22. package/dist/_spawn-stream.d.ts +32 -0
  23. package/dist/_spawn-stream.d.ts.map +1 -0
  24. package/dist/_syntax-check.d.ts +16 -0
  25. package/dist/_syntax-check.d.ts.map +1 -0
  26. package/dist/_util.d.ts +68 -0
  27. package/dist/_util.d.ts.map +1 -0
  28. package/dist/_win32-resolve.d.ts +42 -0
  29. package/dist/_win32-resolve.d.ts.map +1 -0
  30. package/dist/audit.d.ts +4 -5
  31. package/dist/audit.d.ts.map +1 -0
  32. package/dist/audit.js +2743 -28
  33. package/dist/audit.js.map +7 -1
  34. package/dist/bash-kill-guard.d.ts +62 -0
  35. package/dist/bash-kill-guard.d.ts.map +1 -0
  36. package/dist/bash.d.ts +4 -5
  37. package/dist/bash.d.ts.map +1 -0
  38. package/dist/bash.js +59 -30
  39. package/dist/bash.js.map +7 -1
  40. package/dist/batch-tool-use.d.ts +4 -5
  41. package/dist/batch-tool-use.d.ts.map +1 -0
  42. package/dist/batch-tool-use.js +3 -3
  43. package/dist/batch-tool-use.js.map +7 -1
  44. package/dist/browser/artifacts.d.ts +13 -0
  45. package/dist/browser/artifacts.d.ts.map +1 -0
  46. package/dist/browser/index.d.ts +7 -0
  47. package/dist/browser/index.d.ts.map +1 -0
  48. package/dist/browser/index.js +1264 -0
  49. package/dist/browser/index.js.map +7 -0
  50. package/dist/browser/manager.d.ts +57 -0
  51. package/dist/browser/manager.d.ts.map +1 -0
  52. package/dist/browser/network-guard-proxy.d.ts +23 -0
  53. package/dist/browser/network-guard-proxy.d.ts.map +1 -0
  54. package/dist/browser/security.d.ts +29 -0
  55. package/dist/browser/security.d.ts.map +1 -0
  56. package/dist/browser/tools.d.ts +62 -0
  57. package/dist/browser/tools.d.ts.map +1 -0
  58. package/dist/browser/types.d.ts +60 -0
  59. package/dist/browser/types.d.ts.map +1 -0
  60. package/dist/builtin.d.ts +11 -12
  61. package/dist/builtin.d.ts.map +1 -0
  62. package/dist/builtin.js +8099 -2178
  63. package/dist/builtin.js.map +7 -1
  64. package/dist/circuit-breaker.d.ts +4 -5
  65. package/dist/circuit-breaker.d.ts.map +1 -0
  66. package/dist/circuit-breaker.js +3 -3
  67. package/dist/circuit-breaker.js.map +7 -1
  68. package/dist/codebase-index/background-indexer.d.ts +116 -0
  69. package/dist/codebase-index/background-indexer.d.ts.map +1 -0
  70. package/dist/codebase-index/bm25.d.ts +40 -0
  71. package/dist/codebase-index/bm25.d.ts.map +1 -0
  72. package/dist/codebase-index/circuit-breaker.d.ts +94 -0
  73. package/dist/codebase-index/circuit-breaker.d.ts.map +1 -0
  74. package/dist/codebase-index/codebase-index-tool.d.ts +17 -0
  75. package/dist/codebase-index/codebase-index-tool.d.ts.map +1 -0
  76. package/dist/codebase-index/codebase-search-tool.d.ts +43 -0
  77. package/dist/codebase-index/codebase-search-tool.d.ts.map +1 -0
  78. package/dist/codebase-index/codebase-stats-tool.d.ts +23 -0
  79. package/dist/codebase-index/codebase-stats-tool.d.ts.map +1 -0
  80. package/dist/codebase-index/gitignore.d.ts +22 -0
  81. package/dist/codebase-index/gitignore.d.ts.map +1 -0
  82. package/dist/codebase-index/go-parser.d.ts +16 -0
  83. package/dist/codebase-index/go-parser.d.ts.map +1 -0
  84. package/dist/codebase-index/index-service.d.ts +25 -0
  85. package/dist/codebase-index/index-service.d.ts.map +1 -0
  86. package/dist/codebase-index/index.d.ts +23 -271
  87. package/dist/codebase-index/index.d.ts.map +1 -0
  88. package/dist/codebase-index/index.js +136 -68
  89. package/dist/codebase-index/index.js.map +7 -1
  90. package/dist/codebase-index/indexer.d.ts +28 -0
  91. package/dist/codebase-index/indexer.d.ts.map +1 -0
  92. package/dist/codebase-index/json-parser.d.ts +8 -0
  93. package/dist/codebase-index/json-parser.d.ts.map +1 -0
  94. package/dist/codebase-index/lsp-kind.d.ts +53 -0
  95. package/dist/codebase-index/lsp-kind.d.ts.map +1 -0
  96. package/dist/codebase-index/py-parser.d.ts +16 -0
  97. package/dist/codebase-index/py-parser.d.ts.map +1 -0
  98. package/dist/codebase-index/refs-extractor.d.ts +25 -0
  99. package/dist/codebase-index/refs-extractor.d.ts.map +1 -0
  100. package/dist/codebase-index/rs-parser.d.ts +8 -0
  101. package/dist/codebase-index/rs-parser.d.ts.map +1 -0
  102. package/dist/codebase-index/schema.d.ts +82 -0
  103. package/dist/codebase-index/schema.d.ts.map +1 -0
  104. package/dist/codebase-index/ts-parser.d.ts +30 -0
  105. package/dist/codebase-index/ts-parser.d.ts.map +1 -0
  106. package/dist/codebase-index/worker-protocol.d.ts +77 -0
  107. package/dist/codebase-index/worker-protocol.d.ts.map +1 -0
  108. package/dist/codebase-index/worker.d.ts +16 -2
  109. package/dist/codebase-index/worker.d.ts.map +1 -0
  110. package/dist/codebase-index/worker.js +103 -65
  111. package/dist/codebase-index/worker.js.map +7 -1
  112. package/dist/codebase-index/writer.d.ts +189 -0
  113. package/dist/codebase-index/writer.d.ts.map +1 -0
  114. package/dist/codebase-index/yaml-parser.d.ts +8 -0
  115. package/dist/codebase-index/yaml-parser.d.ts.map +1 -0
  116. package/dist/design.d.ts +38 -0
  117. package/dist/design.d.ts.map +1 -0
  118. package/dist/design.js +270 -0
  119. package/dist/design.js.map +7 -0
  120. package/dist/diff.d.ts +4 -5
  121. package/dist/diff.d.ts.map +1 -0
  122. package/dist/diff.js +14 -12
  123. package/dist/diff.js.map +7 -1
  124. package/dist/document.d.ts +4 -5
  125. package/dist/document.d.ts.map +1 -0
  126. package/dist/document.js +8 -7
  127. package/dist/document.js.map +7 -1
  128. package/dist/e2e.d.ts +56 -0
  129. package/dist/e2e.d.ts.map +1 -0
  130. package/dist/edit.d.ts +5 -36
  131. package/dist/edit.d.ts.map +1 -0
  132. package/dist/edit.js +28 -16
  133. package/dist/edit.js.map +7 -1
  134. package/dist/exec.d.ts +67 -2
  135. package/dist/exec.d.ts.map +1 -0
  136. package/dist/exec.js +51 -26
  137. package/dist/exec.js.map +7 -1
  138. package/dist/fetch.d.ts +4 -32
  139. package/dist/fetch.d.ts.map +1 -0
  140. package/dist/fetch.js +89 -77
  141. package/dist/fetch.js.map +7 -1
  142. package/dist/format.d.ts +4 -5
  143. package/dist/format.d.ts.map +1 -0
  144. package/dist/format.js +2415 -28
  145. package/dist/format.js.map +7 -1
  146. package/dist/git.d.ts +4 -5
  147. package/dist/git.d.ts.map +1 -0
  148. package/dist/git.js +10 -9
  149. package/dist/git.js.map +7 -1
  150. package/dist/glob.d.ts +4 -5
  151. package/dist/glob.d.ts.map +1 -0
  152. package/dist/glob.js +15 -12
  153. package/dist/glob.js.map +7 -1
  154. package/dist/grep.d.ts +4 -5
  155. package/dist/grep.d.ts.map +1 -0
  156. package/dist/grep.js +13 -10
  157. package/dist/grep.js.map +7 -1
  158. package/dist/index.d.ts +21 -724
  159. package/dist/index.d.ts.map +1 -0
  160. package/dist/index.js +8955 -2866
  161. package/dist/index.js.map +7 -1
  162. package/dist/install.d.ts +4 -5
  163. package/dist/install.d.ts.map +1 -0
  164. package/dist/install.js +2680 -31
  165. package/dist/install.js.map +7 -1
  166. package/dist/json.d.ts +4 -5
  167. package/dist/json.d.ts.map +1 -0
  168. package/dist/json.js +13 -11
  169. package/dist/json.js.map +7 -1
  170. package/dist/kanban.d.ts +107 -0
  171. package/dist/kanban.d.ts.map +1 -0
  172. package/dist/kanban.js +845 -0
  173. package/dist/kanban.js.map +7 -0
  174. package/dist/languages/detect.d.ts +3 -0
  175. package/dist/languages/detect.d.ts.map +1 -0
  176. package/dist/languages/diagnostics.d.ts +15 -0
  177. package/dist/languages/diagnostics.d.ts.map +1 -0
  178. package/dist/languages/execute-tool.d.ts +34 -0
  179. package/dist/languages/execute-tool.d.ts.map +1 -0
  180. package/dist/languages/execute.d.ts +18 -0
  181. package/dist/languages/execute.d.ts.map +1 -0
  182. package/dist/languages/index.d.ts +12 -0
  183. package/dist/languages/index.d.ts.map +1 -0
  184. package/dist/languages/index.js +4457 -0
  185. package/dist/languages/index.js.map +7 -0
  186. package/dist/languages/legacy-bridge.d.ts +29 -0
  187. package/dist/languages/legacy-bridge.d.ts.map +1 -0
  188. package/dist/languages/package-tool.d.ts +29 -0
  189. package/dist/languages/package-tool.d.ts.map +1 -0
  190. package/dist/languages/plan.d.ts +4 -0
  191. package/dist/languages/plan.d.ts.map +1 -0
  192. package/dist/languages/profile-helpers.d.ts +14 -0
  193. package/dist/languages/profile-helpers.d.ts.map +1 -0
  194. package/dist/languages/profiles/additional.d.ts +3 -0
  195. package/dist/languages/profiles/additional.d.ts.map +1 -0
  196. package/dist/languages/profiles/primary.d.ts +3 -0
  197. package/dist/languages/profiles/primary.d.ts.map +1 -0
  198. package/dist/languages/registry.d.ts +11 -0
  199. package/dist/languages/registry.d.ts.map +1 -0
  200. package/dist/languages/tool.d.ts +32 -0
  201. package/dist/languages/tool.d.ts.map +1 -0
  202. package/dist/languages/types.d.ts +207 -0
  203. package/dist/languages/types.d.ts.map +1 -0
  204. package/dist/lint.d.ts +4 -5
  205. package/dist/lint.d.ts.map +1 -0
  206. package/dist/lint.js +2415 -27
  207. package/dist/lint.js.map +7 -1
  208. package/dist/logs.d.ts +4 -5
  209. package/dist/logs.d.ts.map +1 -0
  210. package/dist/logs.js +16 -10
  211. package/dist/logs.js.map +7 -1
  212. package/dist/memory.d.ts +7 -8
  213. package/dist/memory.d.ts.map +1 -0
  214. package/dist/memory.js +7 -5
  215. package/dist/memory.js.map +7 -1
  216. package/dist/mode.d.ts +4 -5
  217. package/dist/mode.d.ts.map +1 -0
  218. package/dist/mode.js +3 -3
  219. package/dist/mode.js.map +7 -1
  220. package/dist/next-steps.d.ts +5 -6
  221. package/dist/next-steps.d.ts.map +1 -0
  222. package/dist/next-steps.js +5 -4
  223. package/dist/next-steps.js.map +7 -1
  224. package/dist/outdated.d.ts +4 -5
  225. package/dist/outdated.d.ts.map +1 -0
  226. package/dist/outdated.js +4777 -68
  227. package/dist/outdated.js.map +7 -1
  228. package/dist/pack.d.ts +3 -6
  229. package/dist/pack.d.ts.map +1 -0
  230. package/dist/pack.js +8134 -2204
  231. package/dist/pack.js.map +7 -1
  232. package/dist/patch.d.ts +4 -5
  233. package/dist/patch.d.ts.map +1 -0
  234. package/dist/patch.js +18 -16
  235. package/dist/patch.js.map +7 -1
  236. package/dist/plan.d.ts +55 -0
  237. package/dist/plan.d.ts.map +1 -0
  238. package/dist/plan.js +280 -0
  239. package/dist/plan.js.map +7 -0
  240. package/dist/process-guardian.d.ts +100 -0
  241. package/dist/process-guardian.d.ts.map +1 -0
  242. package/dist/process-registry-persistent.d.ts +121 -0
  243. package/dist/process-registry-persistent.d.ts.map +1 -0
  244. package/dist/process-registry.d.ts +13 -17
  245. package/dist/process-registry.d.ts.map +1 -0
  246. package/dist/process-registry.js +15 -9
  247. package/dist/process-registry.js.map +7 -1
  248. package/dist/ps-slash.d.ts +106 -0
  249. package/dist/ps-slash.d.ts.map +1 -0
  250. package/dist/ps-slash.js +1291 -0
  251. package/dist/ps-slash.js.map +7 -0
  252. package/dist/read.d.ts +4 -5
  253. package/dist/read.d.ts.map +1 -0
  254. package/dist/read.js +13 -11
  255. package/dist/read.js.map +7 -1
  256. package/dist/replace.d.ts +4 -5
  257. package/dist/replace.d.ts.map +1 -0
  258. package/dist/replace.js +26 -13
  259. package/dist/replace.js.map +7 -1
  260. package/dist/scaffold.d.ts +4 -5
  261. package/dist/scaffold.d.ts.map +1 -0
  262. package/dist/scaffold.js +16 -14
  263. package/dist/scaffold.js.map +7 -1
  264. package/dist/search.d.ts +5 -6
  265. package/dist/search.d.ts.map +1 -0
  266. package/dist/search.js +32 -27
  267. package/dist/search.js.map +7 -1
  268. package/dist/set-working-dir.d.ts +18 -0
  269. package/dist/set-working-dir.d.ts.map +1 -0
  270. package/dist/shim/node-sqlite.d.ts +3 -0
  271. package/dist/shim/node-sqlite.d.ts.map +1 -0
  272. package/dist/skill.d.ts +45 -0
  273. package/dist/skill.d.ts.map +1 -0
  274. package/dist/skill.js +175 -0
  275. package/dist/skill.js.map +7 -0
  276. package/dist/spawn-background.d.ts +80 -0
  277. package/dist/spawn-background.d.ts.map +1 -0
  278. package/dist/task.d.ts +35 -0
  279. package/dist/task.d.ts.map +1 -0
  280. package/dist/task.js +380 -0
  281. package/dist/task.js.map +7 -0
  282. package/dist/test.d.ts +4 -5
  283. package/dist/test.d.ts.map +1 -0
  284. package/dist/test.js +2419 -27
  285. package/dist/test.js.map +7 -1
  286. package/dist/todo.d.ts +4 -5
  287. package/dist/todo.d.ts.map +1 -0
  288. package/dist/todo.js +5 -5
  289. package/dist/todo.js.map +7 -1
  290. package/dist/tool-diff.d.ts +16 -17
  291. package/dist/tool-diff.d.ts.map +1 -0
  292. package/dist/tool-diff.js +11 -3
  293. package/dist/tool-diff.js.map +7 -1
  294. package/dist/tool-help.d.ts +4 -5
  295. package/dist/tool-help.d.ts.map +1 -0
  296. package/dist/tool-help.js +3 -3
  297. package/dist/tool-help.js.map +7 -1
  298. package/dist/tool-icon-map.d.ts +46 -0
  299. package/dist/tool-icon-map.d.ts.map +1 -0
  300. package/dist/tool-icons.d.ts +5 -6
  301. package/dist/tool-icons.d.ts.map +1 -0
  302. package/dist/tool-icons.js +16 -4
  303. package/dist/tool-icons.js.map +7 -1
  304. package/dist/tool-search.d.ts +4 -5
  305. package/dist/tool-search.d.ts.map +1 -0
  306. package/dist/tool-search.js +3 -3
  307. package/dist/tool-search.js.map +7 -1
  308. package/dist/tool-summary.d.ts +4 -5
  309. package/dist/tool-summary.d.ts.map +1 -0
  310. package/dist/tool-summary.js +5 -3
  311. package/dist/tool-summary.js.map +7 -1
  312. package/dist/tool-use.d.ts +4 -5
  313. package/dist/tool-use.d.ts.map +1 -0
  314. package/dist/tool-use.js +3 -3
  315. package/dist/tool-use.js.map +7 -1
  316. package/dist/tree.d.ts +4 -5
  317. package/dist/tree.d.ts.map +1 -0
  318. package/dist/tree.js +11 -9
  319. package/dist/tree.js.map +7 -1
  320. package/dist/typecheck.d.ts +4 -5
  321. package/dist/typecheck.d.ts.map +1 -0
  322. package/dist/typecheck.js +2416 -28
  323. package/dist/typecheck.js.map +7 -1
  324. package/dist/write.d.ts +4 -5
  325. package/dist/write.d.ts.map +1 -0
  326. package/dist/write.js +21 -16
  327. package/dist/write.js.map +7 -1
  328. package/package.json +39 -6
  329. package/dist/background-indexer-BeDBxfSh.d.ts +0 -371
  330. package/dist/exec-2OKoT6tk.d.ts +0 -121
package/dist/edit.js.map CHANGED
@@ -1 +1,7 @@
1
- {"version":3,"sources":["../src/_edit-match.ts","../src/_syntax-check.ts","../src/_util.ts","../src/edit.ts"],"names":["path","ts","fsp","stat"],"mappings":"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","file":"edit.js","sourcesContent":["/**\n * Matching ladder for the `edit` tool.\n *\n * Models routinely hallucinate whitespace when reproducing code they read\n * many turns ago: trailing spaces appear, indentation shifts by a level,\n * tabs become spaces. A strict exact-match `old_string` fails on all of\n * these even though the intended target is unambiguous. This module\n * implements a graded fallback chain — each tier is strictly more permissive\n * and strictly lower-confidence than the previous one:\n *\n * 1. `exact` — substring match (the classic behavior).\n * 2. `trailing-whitespace` — whole-line window match ignoring trailing\n * whitespace per line. Zero ambiguity risk\n * beyond exact (trailing blanks never carry\n * meaning in the languages we edit).\n * 3. `whitespace-normalized` — whole-line window match ignoring leading\n * AND trailing whitespace per line. The\n * replacement is re-indented to the file's\n * actual indentation.\n * 4. `fuzzy` — block-anchor match: first and last lines\n * must match (trimmed), interior lines are\n * compared by Levenshtein similarity. Only a\n * single candidate with a clear margin over\n * the runner-up is accepted.\n *\n * The caller (edit.ts) enforces per-tier uniqueness, restricts `replace_all`\n * to tiers 1–2, and surfaces the tier + confidence in the tool output so the\n * model and the session log both see when a fallback fired.\n */\n\nexport type MatchTier = 'exact' | 'trailing-whitespace' | 'whitespace-normalized' | 'fuzzy';\n\nexport interface LadderMatch {\n /** Char offset (inclusive) of the match start in the LF-normalized file. */\n start: number;\n /** Char offset (exclusive) of the match end. */\n end: number;\n /** 1-based line number of the first matched line (for error messages). */\n startLine: number;\n}\n\nexport interface LadderResult {\n tier: MatchTier;\n matches: LadderMatch[];\n /** Similarity score of the best candidate — only set for the fuzzy tier. */\n score?: number | undefined;\n /**\n * Fuzzy tier only: true when two candidates scored within the ambiguity\n * margin of each other, so no single target can be trusted. `matches`\n * then contains the rival candidates for the error message.\n */\n ambiguous?: boolean | undefined;\n}\n\n/** Human-readable label per tier, used in notes and error messages. */\nexport const TIER_LABEL: Record<MatchTier, string> = {\n exact: 'exact match',\n 'trailing-whitespace': 'whitespace-normalized match (trailing whitespace ignored)',\n 'whitespace-normalized':\n 'whitespace-normalized match (indentation ignored, replacement re-indented)',\n fuzzy: 'fuzzy match (block anchors exact, interior ≥90% similar)',\n};\n\n/** Confidence per tier, recorded in the tool output note. */\nexport const TIER_CONFIDENCE: Record<MatchTier, 'exact' | 'high' | 'medium' | 'low'> = {\n exact: 'exact',\n 'trailing-whitespace': 'high',\n 'whitespace-normalized': 'medium',\n fuzzy: 'low',\n};\n\n/** Below this many non-whitespace chars, indent-insensitive tiers are too\n * ambiguous to trust (`}` would match everywhere). */\nconst MIN_NORMALIZED_NEEDLE_CHARS = 16;\n/** Fuzzy interior similarity threshold. */\nconst FUZZY_MIN_SIMILARITY = 0.9;\n/** A fuzzy best-candidate must beat the runner-up by at least this margin. */\nconst FUZZY_AMBIGUITY_MARGIN = 0.05;\n/** Cost guard: skip fuzzy comparison on interiors larger than this. */\nconst FUZZY_MAX_INTERIOR_CHARS = 2000;\n\n/**\n * Walk the ladder and return the first tier that produces at least one\n * match. Returns `undefined` when no tier matches.\n */\nexport function findLadderMatches(fileLf: string, oldLf: string): LadderResult | undefined {\n // Tier 1 — exact substring scan (supports sub-line needles).\n const exact: LadderMatch[] = [];\n let idx = fileLf.indexOf(oldLf);\n while (idx !== -1) {\n exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt(fileLf, idx) });\n idx = fileLf.indexOf(oldLf, idx + 1);\n }\n if (exact.length > 0) return { tier: 'exact', matches: exact };\n\n // Line-based tiers operate on whole-line windows.\n const fileLines = fileLf.split('\\n');\n const needleLines = oldLf.split('\\n');\n if (needleLines.length > fileLines.length) return undefined;\n const offsets = lineOffsets(fileLines);\n\n const trailing = windowScan(\n fileLines,\n needleLines,\n offsets,\n (a, b) => a.trimEnd() === b.trimEnd(),\n );\n if (trailing.length > 0) return { tier: 'trailing-whitespace', matches: trailing };\n\n const normalizedLen = needleLines.reduce((n, l) => n + l.trim().length, 0);\n if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return undefined;\n\n const normalized = windowScan(fileLines, needleLines, offsets, (a, b) => a.trim() === b.trim());\n if (normalized.length > 0) return { tier: 'whitespace-normalized', matches: normalized };\n\n return fuzzyScan(fileLines, needleLines, offsets);\n}\n\n/** 1-based line number containing char offset `pos`. */\nfunction lineAt(text: string, pos: number): number {\n let line = 1;\n for (let i = 0; i < pos; i++) {\n if (text.charCodeAt(i) === 0x0a) line++;\n }\n return line;\n}\n\n/** Start char offset of each line in the LF-normalized text. */\nfunction lineOffsets(lines: string[]): number[] {\n const out = new Array<number>(lines.length);\n let pos = 0;\n for (let i = 0; i < lines.length; i++) {\n out[i] = pos;\n pos += (lines[i] as string).length + 1; // +1 for the '\\n'\n }\n return out;\n}\n\nfunction windowToMatch(\n fileLines: string[],\n offsets: number[],\n start: number,\n windowLen: number,\n): LadderMatch {\n const lastLine = start + windowLen - 1;\n return {\n start: offsets[start] as number,\n end: (offsets[lastLine] as number) + (fileLines[lastLine] as string).length,\n startLine: start + 1,\n };\n}\n\n/** Slide a needle-sized window over the file lines with a per-line comparator.\n * Matched windows never overlap (scan resumes after the window). */\nfunction windowScan(\n fileLines: string[],\n needleLines: string[],\n offsets: number[],\n eq: (fileLine: string, needleLine: string) => boolean,\n): LadderMatch[] {\n const n = needleLines.length;\n const out: LadderMatch[] = [];\n for (let i = 0; i + n <= fileLines.length; i++) {\n let all = true;\n for (let j = 0; j < n; j++) {\n if (!eq(fileLines[i + j] as string, needleLines[j] as string)) {\n all = false;\n break;\n }\n }\n if (all) {\n out.push(windowToMatch(fileLines, offsets, i, n));\n i += n - 1; // no overlapping windows\n }\n }\n return out;\n}\n\n/** Block-anchor fuzzy tier: first/last lines trim-exact, interior by\n * Levenshtein similarity. Requires ≥3 needle lines so anchors exist. */\nfunction fuzzyScan(\n fileLines: string[],\n needleLines: string[],\n offsets: number[],\n): LadderResult | undefined {\n const n = needleLines.length;\n if (n < 3) return undefined;\n const firstNeedle = (needleLines[0] as string).trim();\n const lastNeedle = (needleLines[n - 1] as string).trim();\n const needleInterior = needleLines\n .slice(1, -1)\n .map((l) => l.trim())\n .join('\\n');\n if (needleInterior.length > FUZZY_MAX_INTERIOR_CHARS) return undefined;\n\n const candidates: Array<{ match: LadderMatch; score: number }> = [];\n for (let i = 0; i + n <= fileLines.length; i++) {\n if ((fileLines[i] as string).trim() !== firstNeedle) continue;\n if ((fileLines[i + n - 1] as string).trim() !== lastNeedle) continue;\n const windowInterior = fileLines\n .slice(i + 1, i + n - 1)\n .map((l) => l.trim())\n .join('\\n');\n if (windowInterior.length > FUZZY_MAX_INTERIOR_CHARS) continue;\n const score = similarity(needleInterior, windowInterior);\n if (score >= FUZZY_MIN_SIMILARITY) {\n candidates.push({ match: windowToMatch(fileLines, offsets, i, n), score });\n }\n }\n\n if (candidates.length === 0) return undefined;\n candidates.sort((a, b) => b.score - a.score);\n const best = candidates[0] as { match: LadderMatch; score: number };\n const runnerUp = candidates[1];\n if (runnerUp && best.score - runnerUp.score < FUZZY_AMBIGUITY_MARGIN) {\n return {\n tier: 'fuzzy',\n matches: candidates.map((c) => c.match),\n score: best.score,\n ambiguous: true,\n };\n }\n return { tier: 'fuzzy', matches: [best.match], score: best.score };\n}\n\n/** Normalized similarity in [0, 1]: 1 − levenshtein / max-length. */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1;\n const max = Math.max(a.length, b.length);\n if (max === 0) return 1;\n // Quick reject: a length gap larger than the allowed edit budget can\n // never reach the threshold — skip the O(n·m) DP entirely.\n if (Math.abs(a.length - b.length) / max > 1 - FUZZY_MIN_SIMILARITY + 0.05) {\n return 1 - Math.abs(a.length - b.length) / max;\n }\n return 1 - levenshtein(a, b) / max;\n}\n\n/** Two-row Levenshtein distance. */\nfunction levenshtein(a: string, b: string): number {\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n let prev = new Array<number>(b.length + 1);\n let cur = new Array<number>(b.length + 1);\n for (let j = 0; j <= b.length; j++) prev[j] = j;\n for (let i = 1; i <= a.length; i++) {\n cur[0] = i;\n const ca = a.charCodeAt(i - 1);\n for (let j = 1; j <= b.length; j++) {\n const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;\n cur[j] = Math.min(\n (prev[j] as number) + 1,\n (cur[j - 1] as number) + 1,\n (prev[j - 1] as number) + cost,\n );\n }\n [prev, cur] = [cur, prev];\n }\n return prev[b.length] as number;\n}\n\n/** Leading whitespace (spaces/tabs) of the first non-empty line, or ''. */\nexport function firstLineIndent(text: string): string {\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue;\n return (/^[ \\t]*/.exec(line) as RegExpExecArray)[0];\n }\n return '';\n}\n\n/**\n * Shift `newLf` from the needle's indentation to the matched block's actual\n * indentation. Only clean prefix relationships are adjusted (purely adding\n * or removing leading whitespace); a tabs-vs-spaces mismatch is left alone\n * rather than guessed at — the diff in the tool output makes the result\n * visible either way.\n */\nexport function adjustIndent(\n newLf: string,\n fromIndent: string,\n toIndent: string,\n): { text: string; adjusted: boolean } {\n if (fromIndent === toIndent) return { text: newLf, adjusted: false };\n const lines = newLf.split('\\n');\n if (toIndent.startsWith(fromIndent)) {\n const extra = toIndent.slice(fromIndent.length);\n return {\n text: lines.map((l) => (l.trim() === '' ? l : extra + l)).join('\\n'),\n adjusted: true,\n };\n }\n if (fromIndent.startsWith(toIndent)) {\n const remove = fromIndent.slice(toIndent.length);\n return {\n text: lines.map((l) => (l.startsWith(remove) ? l.slice(remove.length) : l)).join('\\n'),\n adjusted: true,\n };\n }\n return { text: newLf, adjusted: false };\n}\n\n/**\n * Best-effort locator for the no-match error: find the window whose trimmed\n * lines are most similar to the needle (cheap common-prefix scoring — this\n * runs on the failure path where the needle by definition doesn't match, so\n * precision matters less than never being slow). Returns the 1-based line\n * and a short snippet the model can use to correct itself without re-reading.\n */\nexport function nearestMatchHint(\n fileLf: string,\n oldLf: string,\n): { line: number; snippet: string } | undefined {\n const fileLines = fileLf.split('\\n');\n const needleLines = oldLf.split('\\n');\n if (needleLines.length > fileLines.length) return undefined;\n\n const needleTrimmed = needleLines.map((l) => l.trim());\n let bestScore = 0;\n let bestStart = -1;\n for (let i = 0; i + needleLines.length <= fileLines.length; i++) {\n let sum = 0;\n for (let j = 0; j < needleLines.length; j++) {\n sum += prefixSimilarity(needleTrimmed[j] as string, (fileLines[i + j] as string).trim());\n }\n const score = sum / needleLines.length;\n if (score > bestScore) {\n bestScore = score;\n bestStart = i;\n }\n }\n if (bestStart === -1 || bestScore < 0.5) return undefined;\n\n const snippet = fileLines\n .slice(bestStart, bestStart + Math.min(3, needleLines.length))\n .map((l) => (l.length > 120 ? `${l.slice(0, 120)}…` : l))\n .join('\\n');\n return { line: bestStart + 1, snippet };\n}\n\n/** Cheap similarity proxy: shared prefix length over max length. */\nfunction prefixSimilarity(a: string, b: string): number {\n if (a === b) return 1;\n const max = Math.max(a.length, b.length);\n if (max === 0) return 1;\n let p = 0;\n const lim = Math.min(a.length, b.length);\n while (p < lim && a.charCodeAt(p) === b.charCodeAt(p)) p++;\n return p / max;\n}\n","/**\n * Post-edit syntax validation for `edit` and `write`.\n *\n * After a mutation lands on disk, the new content is parsed (TS/JS via the\n * TypeScript compiler's parser, JSON via JSON.parse with a JSONC fallback)\n * and any parse errors are surfaced in the tool output. The edit is NOT\n * rolled back — reverting would desynchronize the model's picture of the\n * file — but the errors come back in the same turn so the model fixes the\n * breakage before the user ever opens a broken file.\n *\n * The check is purely syntactic (no type checking, no project program), so\n * it costs single-digit milliseconds per file. Semantic type errors remain\n * the `type-gate` plugin's job.\n */\nimport * as path from 'node:path';\n\nconst TS_LIKE = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs']);\n/** Skip pathologically large files — the model never writes these in one go. */\nconst MAX_CHECK_CHARS = 1_500_000;\n/** Cap reported errors so a mangled file doesn't flood the context. */\nconst MAX_ERRORS = 5;\n\ntype Ts = typeof import('typescript');\n\nlet tsLoad: Promise<Ts | null> | null = null;\n/** Lazy-load the TypeScript compiler (already a dependency via the codebase\n * indexer) so tools that never touch TS files don't pay its import cost. */\nfunction loadTypescript(): Promise<Ts | null> {\n tsLoad ??= import('typescript').then(\n (m) => ((m as unknown as { default?: Ts }).default ?? m) as Ts,\n () => null,\n );\n return tsLoad;\n}\n\nexport interface SyntaxCheckResult {\n /** Parse errors in the new content (capped at {@link MAX_ERRORS}). */\n errors: string[];\n /** True when the pre-edit content already failed to parse — the edit did\n * not introduce the breakage (though it didn't fix it either). */\n preExisting: boolean;\n}\n\n/**\n * Check `content` for syntax errors based on the file extension.\n * Returns `undefined` when the file type is not checkable (or the checker\n * is unavailable) and a result with an empty `errors` array when clean.\n * Pass `previousContent` to distinguish newly-introduced errors from\n * pre-existing ones.\n */\nexport async function checkSyntax(\n filePath: string,\n content: string,\n previousContent?: string,\n): Promise<SyntaxCheckResult | undefined> {\n const errors = await parseErrors(filePath, content);\n if (errors === undefined) return undefined;\n if (errors.length === 0) return { errors, preExisting: false };\n let preExisting = false;\n if (previousContent !== undefined) {\n const prevErrors = await parseErrors(filePath, previousContent);\n preExisting = prevErrors !== undefined && prevErrors.length > 0;\n }\n return { errors, preExisting };\n}\n\n/** Files that are conventionally JSONC (comments + trailing commas allowed). */\nfunction isJsoncFile(filePath: string): boolean {\n const base = path.basename(filePath).toLowerCase();\n if (base.endsWith('.jsonc')) return true;\n if (/^(tsconfig|jsconfig)([.-].*)?\\.json$/.test(base)) return true;\n const dir = path.basename(path.dirname(filePath)).toLowerCase();\n return dir === '.vscode';\n}\n\nasync function parseErrors(filePath: string, content: string): Promise<string[] | undefined> {\n if (content.length > MAX_CHECK_CHARS) return undefined;\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext === '.json' || ext === '.jsonc') {\n try {\n JSON.parse(content);\n return [];\n } catch (err) {\n // Known-JSONC files (tsconfig.json, VS Code settings, *.jsonc) accept\n // comments and trailing commas — re-parse with the TS config parser\n // before declaring them broken. Plain .json stays strict: a trailing\n // comma there IS an error the model should fix.\n if (isJsoncFile(filePath)) {\n const ts = await loadTypescript();\n if (ts) {\n const jsonc = ts.parseConfigFileTextToJson(filePath, content);\n if (!jsonc.error) return [];\n return [formatDiag(ts, jsonc.error, content)];\n }\n }\n return [`JSON parse error: ${(err as Error).message}`];\n }\n }\n\n if (!TS_LIKE.has(ext)) return undefined;\n const ts = await loadTypescript();\n if (!ts) return undefined;\n\n const scriptKind =\n ext === '.tsx'\n ? ts.ScriptKind.TSX\n : ext === '.ts' || ext === '.mts' || ext === '.cts'\n ? ts.ScriptKind.TS\n : // Plain JS may legitimately contain JSX; the JSX grammar is a\n // superset for untyped code, so parsing .js/.jsx as JSX avoids\n // false positives on React files.\n ts.ScriptKind.JSX;\n\n const sourceFile = ts.createSourceFile(\n path.basename(filePath),\n content,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n scriptKind,\n );\n // parseDiagnostics is not in the public .d.ts but has been the stable\n // home of the parser's syntactic diagnostics for a decade (the public\n // alternative, program.getSyntacticDiagnostics, needs a full Program).\n const diags =\n (sourceFile as unknown as { parseDiagnostics?: import('typescript').Diagnostic[] })\n .parseDiagnostics ?? [];\n return diags.slice(0, MAX_ERRORS).map((d) => formatDiag(ts, d, content, sourceFile));\n}\n\nfunction formatDiag(\n ts: Ts,\n diag: import('typescript').Diagnostic,\n content: string,\n sourceFile?: import('typescript').SourceFile,\n): string {\n const message = ts.flattenDiagnosticMessageText(diag.messageText, ' ');\n if (diag.start === undefined) return message;\n let line: number;\n if (sourceFile) {\n line = sourceFile.getLineAndCharacterOfPosition(diag.start).line + 1;\n } else {\n line = 1;\n for (let i = 0; i < diag.start && i < content.length; i++) {\n if (content.charCodeAt(i) === 0x0a) line++;\n }\n }\n return `line ${line}: ${message}`;\n}\n","import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` — the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm → yarn → npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root→out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink — macOS `/var`→`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n…[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]…\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// ─── Command-output normalization (token-saving) ────────────────────────────\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) — never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only — it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `… ⟨repeated ${run}×⟩`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends — the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n…[truncated ${total - kept} bytes]…\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI → collapse\n * carriage-return progress → trim trailing whitespace → collapse identical\n * consecutive lines → squeeze blank-line runs → head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines → 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n","import * as fs from 'node:fs/promises';\nimport type { Tool } from '@wrongstack/core';\nimport {\n atomicWrite,\n detectNewlineStyle,\n normalizeToLf,\n ToolValidationError,\n toStyle,\n unifiedDiff,\n} from '@wrongstack/core';\nimport {\n adjustIndent,\n findLadderMatches,\n firstLineIndent,\n type MatchTier,\n nearestMatchHint,\n TIER_CONFIDENCE,\n TIER_LABEL,\n} from './_edit-match.js';\nimport { checkSyntax } from './_syntax-check.js';\nimport { safeResolveReal, sha256hex } from './_util.js';\n\ninterface EditInput {\n path: string;\n old_string: string;\n new_string: string;\n /**\n * When true, replaces all occurrences of `old_string`.\n * When false (default), replaces only the first occurrence and errors\n * if more than one match exists — use this to ensure you target the\n * right location.\n */\n replace_all?: boolean | undefined;\n}\n\ninterface EditOutput {\n path: string;\n replacements: number;\n diff: string;\n /**\n * How `old_string` was located. Anything other than 'exact' means a\n * whitespace/fuzzy fallback fired — the note carries the confidence level\n * and the diff shows exactly what was replaced.\n */\n matched_by: MatchTier;\n /**\n * Parse errors found in the file AFTER the edit was applied (TS/JS/JSON\n * only). The edit is still on disk — fix these in a follow-up edit now,\n * before doing anything else.\n */\n syntax_errors?: string[] | undefined;\n note?: string | undefined;\n}\n\nexport const editTool: Tool<EditInput, EditOutput> = {\n name: 'edit',\n category: 'Filesystem',\n description:\n 'Perform a precise, surgical text replacement in a file. This is the preferred tool for modifying existing code. ' +\n 'It works best after a prior `read`, but can auto-read the current file when the replacement is still unambiguous. ' +\n 'Fails safely if the `old_string` appears more than once unless `replace_all` is set.',\n usageHint:\n 'RECOMMENDED WORKFLOW:\\n' +\n '1. Prefer calling `read` on the target file first when planning an edit.\\n' +\n '2. Use a sufficiently unique `old_string` (include surrounding lines/context if needed).\\n' +\n '3. If the string appears multiple times and you want to change all of them, set `replace_all: true`.\\n' +\n '4. `new_string` must be the exact replacement text.\\n\\n' +\n 'If no prior read is recorded, the tool auto-reads the current file and only applies the edit after the same ambiguity checks pass.\\n' +\n 'If `old_string` differs from the file only in whitespace (trailing spaces, indentation), a lower-confidence fallback match is applied and reported in `matched_by`/`note` — always verify the diff when this happens.\\n' +\n 'After the edit, TS/JS/JSON files are syntax-checked; if `syntax_errors` is present in the output, fix those errors immediately with a follow-up edit.',\n selection: {\n doNotUseWhen: 'creating a new file, replacing the whole file, or applying an existing unified diff.',\n useInstead: ['write', 'patch'],\n },\n permission: 'confirm',\n mutating: true,\n capabilities: ['fs.write'],\n icon: 'edit',\n timeoutMs: 5_000,\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string' },\n old_string: { type: 'string' },\n new_string: { type: 'string' },\n replace_all: { type: 'boolean' },\n },\n required: ['path', 'old_string', 'new_string'],\n },\n async execute(input, ctx, opts) {\n if (!input?.path) {\n throw new ToolValidationError({ message: 'edit: path is required', field: 'path' });\n }\n if (input.old_string === undefined) {\n throw new ToolValidationError({\n message: 'edit: old_string is required',\n field: 'old_string',\n });\n }\n if (input.new_string === undefined) {\n throw new ToolValidationError({\n message: 'edit: new_string is required',\n field: 'new_string',\n });\n }\n if (input.old_string === '') {\n throw new ToolValidationError({\n message: 'edit: old_string cannot be empty',\n field: 'old_string',\n });\n }\n\n const absPath = await safeResolveReal(input.path, ctx);\n const stat = await fs.stat(absPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" does not exist. Use \\`write\\` instead.`,\n field: 'path',\n context: { exists: false },\n });\n }\n throw err;\n });\n if (!stat.isFile()) {\n throw new ToolValidationError({\n message: `edit: \"${input.path}\" is not a regular file`,\n field: 'path',\n });\n }\n\n const autoRead = !ctx.hasRead(absPath);\n // Read BEFORE mtime check to eliminate TOCTOU window.\n // The sequence must be: read content → check mtime → apply edit.\n // If we check mtime first, a concurrent modification between the\n // stat call and the read gives us stale content to search/replace.\n const original = await fs.readFile(absPath, 'utf8');\n const updated = await fs.stat(absPath);\n const mtimeTolerance = process.platform === 'win32' ? 2000 : 1;\n const originalHash = sha256hex(original);\n // Optional call: embedders and older test fixtures may hand in a\n // duck-typed Context without hash tracking — they fall back to mtime.\n const lastReadHash = ctx.lastReadHash?.(absPath);\n if (lastReadHash !== undefined) {\n // Content hash is the authoritative arbiter when available: it closes\n // the mtime tolerance window (an external write within 2 s of the read\n // on Windows is invisible to mtime) and ignores content-preserving\n // mtime bumps (touch, checkout of identical content).\n if (lastReadHash !== originalHash) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" was modified externally. Re-read it first.`,\n field: 'path',\n context: { reason: 'external_modification' },\n });\n }\n } else {\n const lastReadMtime = ctx.lastReadMtime(absPath);\n if (lastReadMtime !== undefined && updated.mtimeMs > lastReadMtime + mtimeTolerance) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" was modified externally. Re-read it first.`,\n field: 'path',\n context: { reason: 'external_modification' },\n });\n }\n }\n if (autoRead && updated.mtimeMs > stat.mtimeMs + mtimeTolerance) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" changed while being auto-read. Retry the edit.`,\n field: 'path',\n context: { reason: 'auto_read_race' },\n });\n }\n const autoReadNote = autoRead\n ? `No prior read was recorded for \"${input.path}\"; edit auto-read the current file and applied the replacement only after the ambiguity checks passed.`\n : undefined;\n const style = detectNewlineStyle(original);\n const fileLf = normalizeToLf(original);\n const oldLf = normalizeToLf(input.old_string);\n const newLf = normalizeToLf(input.new_string);\n\n if (oldLf === newLf) {\n if (autoRead) ctx.recordRead(absPath, updated.mtimeMs, 'user', originalHash);\n return {\n path: absPath,\n replacements: 0,\n diff: '(no-op: old and new are identical)',\n matched_by: 'exact',\n note: autoReadNote,\n };\n }\n\n const ladder = findLadderMatches(fileLf, oldLf);\n\n if (!ladder) {\n const hint = nearestMatchHint(fileLf, oldLf);\n throw new ToolValidationError({\n message: `edit: no match for old_string in \"${input.path}\".${\n hint\n ? ` Nearest match near line ${hint.line}:\\n${hint.snippet}\\n` +\n `Compare this against your old_string and retry with the file's actual text.`\n : ''\n }`,\n field: 'old_string',\n });\n }\n\n const { tier, matches } = ladder;\n const count = matches.length;\n\n if (ladder.ambiguous) {\n const lines = matches.map((m) => m.startLine);\n throw new ToolValidationError({\n message:\n `edit: old_string only matched fuzzily and ${count} candidate blocks scored too close ` +\n `to distinguish (lines: ${lines.join(', ')}) in \"${input.path}\". ` +\n `Re-read the file and use the exact text of the intended block.`,\n field: 'old_string',\n context: { occurrences: count, matchTier: tier },\n });\n }\n\n // Fuzzy/indent-insensitive matches are single-target operations: applying\n // them in bulk multiplies a low-confidence guess. replace_all therefore\n // requires at least a trailing-whitespace-level match.\n if (input.replace_all && tier !== 'exact' && tier !== 'trailing-whitespace') {\n throw new ToolValidationError({\n message:\n `edit: old_string only matched via ${TIER_LABEL[tier]} in \"${input.path}\", ` +\n `but replace_all requires an exact (or trailing-whitespace) match. ` +\n `Re-read the file and use its exact text.`,\n field: 'old_string',\n context: { matchTier: tier },\n });\n }\n\n if (count > 1 && !input.replace_all) {\n const lines = matches.map((m) => m.startLine);\n throw new ToolValidationError({\n message:\n `edit: old_string matched ${count} times in \"${input.path}\" (lines: ${lines.join(', ')})` +\n `${tier === 'exact' ? '' : ` via ${TIER_LABEL[tier]}`}. ` +\n `Add more context to make it unique, or set replace_all: true.`,\n field: 'old_string',\n context: { occurrences: count, matchTier: tier },\n });\n }\n\n let newFileLf: string;\n let tierNote: string | undefined;\n if (tier === 'exact') {\n newFileLf = input.replace_all\n ? fileLf.split(oldLf).join(newLf)\n : fileLf.replace(oldLf, newLf);\n } else {\n // Fallback tiers matched whole-line windows — splice by char offsets,\n // right to left so earlier offsets stay valid. For indent-insensitive\n // tiers, shift the replacement to the file's actual indentation.\n let replacement = newLf;\n let indentSuffix = '';\n if (tier === 'whitespace-normalized' || tier === 'fuzzy') {\n const first = matches[0] as (typeof matches)[number];\n const matchedText = fileLf.slice(first.start, first.end);\n const adjusted = adjustIndent(newLf, firstLineIndent(oldLf), firstLineIndent(matchedText));\n replacement = adjusted.text;\n if (adjusted.adjusted) indentSuffix = '; replacement re-indented to match the file';\n }\n newFileLf = fileLf;\n const applied = input.replace_all ? matches : matches.slice(0, 1);\n for (let i = applied.length - 1; i >= 0; i--) {\n const m = applied[i] as (typeof matches)[number];\n newFileLf = newFileLf.slice(0, m.start) + replacement + newFileLf.slice(m.end);\n }\n const scoreSuffix =\n ladder.score !== undefined ? `, similarity ${(ladder.score * 100).toFixed(1)}%` : '';\n tierNote =\n `old_string did not match exactly; applied via ${TIER_LABEL[tier]} at line ` +\n `${(matches[0] as (typeof matches)[number]).startLine} ` +\n `(confidence: ${TIER_CONFIDENCE[tier]}${scoreSuffix}${indentSuffix}). ` +\n `Review the diff to confirm the intended target was edited.`;\n }\n const newFile = toStyle(newFileLf, style);\n\n // Last exit before mutating the filesystem: a run aborted during the\n // read/match phase must not leave the edit behind. (atomicWrite itself\n // is all-or-nothing — the file is old or new, never partial.)\n opts?.signal?.throwIfAborted();\n await atomicWrite(absPath, newFile, { mode: updated.mode & 0o777 });\n const written = await fs.stat(absPath);\n // Record mtime + content hash so a later edit detects external\n // modification, but tag as 'write' so the permission policy's\n // write-smart-bypass does NOT treat this as \"user already saw the\n // content\" (P1 #1).\n ctx.recordRead(absPath, written.mtimeMs, 'write', sha256hex(newFile));\n\n // Record for session rewind\n ctx.session.recordFileChange({\n path: absPath,\n action: 'modified',\n before: original,\n after: newFile,\n });\n\n const diff = unifiedDiff(original, newFile, {\n fromFile: input.path,\n toFile: input.path,\n });\n\n // Post-edit syntax validation (TS/JS/JSON). The edit stays on disk —\n // errors come back in the same turn so the model fixes them immediately.\n const syntax = await checkSyntax(absPath, newFile, original).catch(() => undefined);\n let syntaxNote: string | undefined;\n if (syntax && syntax.errors.length > 0) {\n syntaxNote = syntax.preExisting\n ? `Syntax check: the file still has parse errors (they pre-date this edit) — see syntax_errors.`\n : `Syntax check: this edit introduced ${syntax.errors.length} parse error(s) — fix them now, see syntax_errors.`;\n }\n\n const notes = [autoReadNote, tierNote, syntaxNote].filter(Boolean);\n\n return {\n path: absPath,\n replacements: input.replace_all ? count : 1,\n diff,\n matched_by: tier,\n syntax_errors: syntax && syntax.errors.length > 0 ? syntax.errors : undefined,\n note: notes.length > 0 ? notes.join('\\n') : undefined,\n };\n },\n};\n"]}
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/edit.ts", "../src/_edit-match.ts", "../src/_syntax-check.ts", "../src/_util.ts"],
4
+ "sourcesContent": ["import * as fs from 'node:fs/promises';\nimport type { Tool } from '@wrongstack/core';\nimport {\n atomicWrite,\n detectNewlineStyle,\n normalizeToLf,\n ToolValidationError,\n toStyle,\n unifiedDiff,\n} from '@wrongstack/core';\nimport {\n adjustIndent,\n findLadderMatches,\n firstLineIndent,\n type MatchTier,\n nearestMatchHint,\n TIER_CONFIDENCE,\n TIER_LABEL,\n} from './_edit-match.js';\nimport { checkSyntax } from './_syntax-check.js';\nimport { safeResolveReal, sha256hex } from './_util.js';\n\ninterface EditInput {\n path: string;\n old_string: string;\n new_string: string;\n /**\n * When true, replaces all occurrences of `old_string`.\n * When false (default), replaces only the first occurrence and errors\n * if more than one match exists \u2014 use this to ensure you target the\n * right location.\n */\n replace_all?: boolean | undefined;\n}\n\ninterface EditOutput {\n path: string;\n replacements: number;\n diff: string;\n /**\n * How `old_string` was located. Anything other than 'exact' means a\n * whitespace/fuzzy fallback fired \u2014 the note carries the confidence level\n * and the diff shows exactly what was replaced.\n */\n matched_by: MatchTier;\n /**\n * Parse errors found in the file AFTER the edit was applied (TS/JS/JSON\n * only). The edit is still on disk \u2014 fix these in a follow-up edit now,\n * before doing anything else.\n */\n syntax_errors?: string[] | undefined;\n note?: string | undefined;\n}\n\nexport const editTool: Tool<EditInput, EditOutput> = {\n name: 'edit',\n category: 'Filesystem',\n description:\n 'Perform a precise, surgical text replacement in a file. This is the preferred tool for modifying existing code. ' +\n 'It works best after a prior `read`, but can auto-read the current file when the replacement is still unambiguous. ' +\n 'Fails safely if the `old_string` appears more than once unless `replace_all` is set.',\n usageHint:\n 'RECOMMENDED WORKFLOW:\\n' +\n '1. Prefer calling `read` on the target file first when planning an edit.\\n' +\n '2. Use a sufficiently unique `old_string` (include surrounding lines/context if needed).\\n' +\n '3. If the string appears multiple times and you want to change all of them, set `replace_all: true`.\\n' +\n '4. `new_string` must be the exact replacement text.\\n\\n' +\n 'If no prior read is recorded, the tool auto-reads the current file and only applies the edit after the same ambiguity checks pass.\\n' +\n 'If `old_string` differs from the file only in whitespace (trailing spaces, indentation), a lower-confidence fallback match is applied and reported in `matched_by`/`note` \u2014 always verify the diff when this happens.\\n' +\n 'After the edit, TS/JS/JSON files are syntax-checked; if `syntax_errors` is present in the output, fix those errors immediately with a follow-up edit.',\n selection: {\n doNotUseWhen: 'creating a new file, replacing the whole file, or applying an existing unified diff.',\n useInstead: ['write', 'patch'],\n },\n permission: 'confirm',\n mutating: true,\n capabilities: ['fs.write'],\n icon: 'edit',\n timeoutMs: 5_000,\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string' },\n old_string: { type: 'string' },\n new_string: { type: 'string' },\n replace_all: { type: 'boolean' },\n },\n required: ['path', 'old_string', 'new_string'],\n },\n async execute(input, ctx, opts) {\n if (!input?.path) {\n throw new ToolValidationError({ message: 'edit: path is required', field: 'path' });\n }\n if (input.old_string === undefined) {\n throw new ToolValidationError({\n message: 'edit: old_string is required',\n field: 'old_string',\n });\n }\n if (input.new_string === undefined) {\n throw new ToolValidationError({\n message: 'edit: new_string is required',\n field: 'new_string',\n });\n }\n if (input.old_string === '') {\n throw new ToolValidationError({\n message: 'edit: old_string cannot be empty',\n field: 'old_string',\n });\n }\n\n const absPath = await safeResolveReal(input.path, ctx);\n const stat = await fs.stat(absPath).catch((err) => {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" does not exist. Use \\`write\\` instead.`,\n field: 'path',\n context: { exists: false },\n });\n }\n throw err;\n });\n if (!stat.isFile()) {\n throw new ToolValidationError({\n message: `edit: \"${input.path}\" is not a regular file`,\n field: 'path',\n });\n }\n\n const autoRead = !ctx.hasRead(absPath);\n // Read BEFORE mtime check to eliminate TOCTOU window.\n // The sequence must be: read content \u2192 check mtime \u2192 apply edit.\n // If we check mtime first, a concurrent modification between the\n // stat call and the read gives us stale content to search/replace.\n const original = await fs.readFile(absPath, 'utf8');\n const updated = await fs.stat(absPath);\n const mtimeTolerance = process.platform === 'win32' ? 2000 : 1;\n const originalHash = sha256hex(original);\n // Optional call: embedders and older test fixtures may hand in a\n // duck-typed Context without hash tracking \u2014 they fall back to mtime.\n const lastReadHash = ctx.lastReadHash?.(absPath);\n if (lastReadHash !== undefined) {\n // Content hash is the authoritative arbiter when available: it closes\n // the mtime tolerance window (an external write within 2 s of the read\n // on Windows is invisible to mtime) and ignores content-preserving\n // mtime bumps (touch, checkout of identical content).\n if (lastReadHash !== originalHash) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" was modified externally. Re-read it first.`,\n field: 'path',\n context: { reason: 'external_modification' },\n });\n }\n } else {\n const lastReadMtime = ctx.lastReadMtime(absPath);\n if (lastReadMtime !== undefined && updated.mtimeMs > lastReadMtime + mtimeTolerance) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" was modified externally. Re-read it first.`,\n field: 'path',\n context: { reason: 'external_modification' },\n });\n }\n }\n if (autoRead && updated.mtimeMs > stat.mtimeMs + mtimeTolerance) {\n throw new ToolValidationError({\n message: `edit: file \"${input.path}\" changed while being auto-read. Retry the edit.`,\n field: 'path',\n context: { reason: 'auto_read_race' },\n });\n }\n const autoReadNote = autoRead\n ? `No prior read was recorded for \"${input.path}\"; edit auto-read the current file and applied the replacement only after the ambiguity checks passed.`\n : undefined;\n const style = detectNewlineStyle(original);\n const fileLf = normalizeToLf(original);\n const oldLf = normalizeToLf(input.old_string);\n const newLf = normalizeToLf(input.new_string);\n\n if (oldLf === newLf) {\n if (autoRead) ctx.recordRead(absPath, updated.mtimeMs, 'user', originalHash);\n return {\n path: absPath,\n replacements: 0,\n diff: '(no-op: old and new are identical)',\n matched_by: 'exact',\n note: autoReadNote,\n };\n }\n\n const ladder = findLadderMatches(fileLf, oldLf);\n\n if (!ladder) {\n const hint = nearestMatchHint(fileLf, oldLf);\n throw new ToolValidationError({\n message: `edit: no match for old_string in \"${input.path}\".${\n hint\n ? ` Nearest match near line ${hint.line}:\\n${hint.snippet}\\n` +\n `Compare this against your old_string and retry with the file's actual text.`\n : ''\n }`,\n field: 'old_string',\n });\n }\n\n const { tier, matches } = ladder;\n const count = matches.length;\n\n if (ladder.ambiguous) {\n const lines = matches.map((m) => m.startLine);\n throw new ToolValidationError({\n message:\n `edit: old_string only matched fuzzily and ${count} candidate blocks scored too close ` +\n `to distinguish (lines: ${lines.join(', ')}) in \"${input.path}\". ` +\n `Re-read the file and use the exact text of the intended block.`,\n field: 'old_string',\n context: { occurrences: count, matchTier: tier },\n });\n }\n\n // Fuzzy/indent-insensitive matches are single-target operations: applying\n // them in bulk multiplies a low-confidence guess. replace_all therefore\n // requires at least a trailing-whitespace-level match.\n if (input.replace_all && tier !== 'exact' && tier !== 'trailing-whitespace') {\n throw new ToolValidationError({\n message:\n `edit: old_string only matched via ${TIER_LABEL[tier]} in \"${input.path}\", ` +\n `but replace_all requires an exact (or trailing-whitespace) match. ` +\n `Re-read the file and use its exact text.`,\n field: 'old_string',\n context: { matchTier: tier },\n });\n }\n\n if (count > 1 && !input.replace_all) {\n const lines = matches.map((m) => m.startLine);\n throw new ToolValidationError({\n message:\n `edit: old_string matched ${count} times in \"${input.path}\" (lines: ${lines.join(', ')})` +\n `${tier === 'exact' ? '' : ` via ${TIER_LABEL[tier]}`}. ` +\n `Add more context to make it unique, or set replace_all: true.`,\n field: 'old_string',\n context: { occurrences: count, matchTier: tier },\n });\n }\n\n let newFileLf: string;\n let tierNote: string | undefined;\n if (tier === 'exact') {\n newFileLf = input.replace_all\n ? fileLf.split(oldLf).join(newLf)\n : fileLf.replace(oldLf, newLf);\n } else {\n // Fallback tiers matched whole-line windows \u2014 splice by char offsets,\n // right to left so earlier offsets stay valid. For indent-insensitive\n // tiers, shift the replacement to the file's actual indentation.\n let replacement = newLf;\n let indentSuffix = '';\n if (tier === 'whitespace-normalized' || tier === 'fuzzy') {\n const first = matches[0] as (typeof matches)[number];\n const matchedText = fileLf.slice(first.start, first.end);\n const adjusted = adjustIndent(newLf, firstLineIndent(oldLf), firstLineIndent(matchedText));\n replacement = adjusted.text;\n if (adjusted.adjusted) indentSuffix = '; replacement re-indented to match the file';\n }\n newFileLf = fileLf;\n const applied = input.replace_all ? matches : matches.slice(0, 1);\n for (let i = applied.length - 1; i >= 0; i--) {\n const m = applied[i] as (typeof matches)[number];\n newFileLf = newFileLf.slice(0, m.start) + replacement + newFileLf.slice(m.end);\n }\n const scoreSuffix =\n ladder.score !== undefined ? `, similarity ${(ladder.score * 100).toFixed(1)}%` : '';\n tierNote =\n `old_string did not match exactly; applied via ${TIER_LABEL[tier]} at line ` +\n `${(matches[0] as (typeof matches)[number]).startLine} ` +\n `(confidence: ${TIER_CONFIDENCE[tier]}${scoreSuffix}${indentSuffix}). ` +\n `Review the diff to confirm the intended target was edited.`;\n }\n const newFile = toStyle(newFileLf, style);\n\n // Last exit before mutating the filesystem: a run aborted during the\n // read/match phase must not leave the edit behind. (atomicWrite itself\n // is all-or-nothing \u2014 the file is old or new, never partial.)\n opts?.signal?.throwIfAborted();\n await atomicWrite(absPath, newFile, { mode: updated.mode & 0o777 });\n const written = await fs.stat(absPath);\n // Record mtime + content hash so a later edit detects external\n // modification, but tag as 'write' so the permission policy's\n // write-smart-bypass does NOT treat this as \"user already saw the\n // content\" (P1 #1).\n ctx.recordRead(absPath, written.mtimeMs, 'write', sha256hex(newFile));\n\n // Record for session rewind\n ctx.session.recordFileChange({\n path: absPath,\n action: 'modified',\n before: original,\n after: newFile,\n });\n\n const diff = unifiedDiff(original, newFile, {\n fromFile: input.path,\n toFile: input.path,\n });\n\n // Post-edit syntax validation (TS/JS/JSON). The edit stays on disk \u2014\n // errors come back in the same turn so the model fixes them immediately.\n const syntax = await checkSyntax(absPath, newFile, original).catch(() => undefined);\n let syntaxNote: string | undefined;\n if (syntax && syntax.errors.length > 0) {\n syntaxNote = syntax.preExisting\n ? `Syntax check: the file still has parse errors (they pre-date this edit) \u2014 see syntax_errors.`\n : `Syntax check: this edit introduced ${syntax.errors.length} parse error(s) \u2014 fix them now, see syntax_errors.`;\n }\n\n const notes = [autoReadNote, tierNote, syntaxNote].filter(Boolean);\n\n return {\n path: absPath,\n replacements: input.replace_all ? count : 1,\n diff,\n matched_by: tier,\n syntax_errors: syntax && syntax.errors.length > 0 ? syntax.errors : undefined,\n note: notes.length > 0 ? notes.join('\\n') : undefined,\n };\n },\n};\n", "/**\n * Matching ladder for the `edit` tool.\n *\n * Models routinely hallucinate whitespace when reproducing code they read\n * many turns ago: trailing spaces appear, indentation shifts by a level,\n * tabs become spaces. A strict exact-match `old_string` fails on all of\n * these even though the intended target is unambiguous. This module\n * implements a graded fallback chain \u2014 each tier is strictly more permissive\n * and strictly lower-confidence than the previous one:\n *\n * 1. `exact` \u2014 substring match (the classic behavior).\n * 2. `trailing-whitespace` \u2014 whole-line window match ignoring trailing\n * whitespace per line. Zero ambiguity risk\n * beyond exact (trailing blanks never carry\n * meaning in the languages we edit).\n * 3. `whitespace-normalized` \u2014 whole-line window match ignoring leading\n * AND trailing whitespace per line. The\n * replacement is re-indented to the file's\n * actual indentation.\n * 4. `fuzzy` \u2014 block-anchor match: first and last lines\n * must match (trimmed), interior lines are\n * compared by Levenshtein similarity. Only a\n * single candidate with a clear margin over\n * the runner-up is accepted.\n *\n * The caller (edit.ts) enforces per-tier uniqueness, restricts `replace_all`\n * to tiers 1\u20132, and surfaces the tier + confidence in the tool output so the\n * model and the session log both see when a fallback fired.\n */\n\nexport type MatchTier = 'exact' | 'trailing-whitespace' | 'whitespace-normalized' | 'fuzzy';\n\nexport interface LadderMatch {\n /** Char offset (inclusive) of the match start in the LF-normalized file. */\n start: number;\n /** Char offset (exclusive) of the match end. */\n end: number;\n /** 1-based line number of the first matched line (for error messages). */\n startLine: number;\n}\n\nexport interface LadderResult {\n tier: MatchTier;\n matches: LadderMatch[];\n /** Similarity score of the best candidate \u2014 only set for the fuzzy tier. */\n score?: number | undefined;\n /**\n * Fuzzy tier only: true when two candidates scored within the ambiguity\n * margin of each other, so no single target can be trusted. `matches`\n * then contains the rival candidates for the error message.\n */\n ambiguous?: boolean | undefined;\n}\n\n/** Human-readable label per tier, used in notes and error messages. */\nexport const TIER_LABEL: Record<MatchTier, string> = {\n exact: 'exact match',\n 'trailing-whitespace': 'whitespace-normalized match (trailing whitespace ignored)',\n 'whitespace-normalized':\n 'whitespace-normalized match (indentation ignored, replacement re-indented)',\n fuzzy: 'fuzzy match (block anchors exact, interior \u226590% similar)',\n};\n\n/** Confidence per tier, recorded in the tool output note. */\nexport const TIER_CONFIDENCE: Record<MatchTier, 'exact' | 'high' | 'medium' | 'low'> = {\n exact: 'exact',\n 'trailing-whitespace': 'high',\n 'whitespace-normalized': 'medium',\n fuzzy: 'low',\n};\n\n/** Below this many non-whitespace chars, indent-insensitive tiers are too\n * ambiguous to trust (`}` would match everywhere). */\nconst MIN_NORMALIZED_NEEDLE_CHARS = 16;\n/** Fuzzy interior similarity threshold. */\nconst FUZZY_MIN_SIMILARITY = 0.9;\n/** A fuzzy best-candidate must beat the runner-up by at least this margin. */\nconst FUZZY_AMBIGUITY_MARGIN = 0.05;\n/** Cost guard: skip fuzzy comparison on interiors larger than this. */\nconst FUZZY_MAX_INTERIOR_CHARS = 2000;\n\n/**\n * Walk the ladder and return the first tier that produces at least one\n * match. Returns `undefined` when no tier matches.\n */\nexport function findLadderMatches(fileLf: string, oldLf: string): LadderResult | undefined {\n // Tier 1 \u2014 exact substring scan (supports sub-line needles).\n const exact: LadderMatch[] = [];\n let idx = fileLf.indexOf(oldLf);\n while (idx !== -1) {\n exact.push({ start: idx, end: idx + oldLf.length, startLine: lineAt(fileLf, idx) });\n idx = fileLf.indexOf(oldLf, idx + 1);\n }\n if (exact.length > 0) return { tier: 'exact', matches: exact };\n\n // Line-based tiers operate on whole-line windows.\n const fileLines = fileLf.split('\\n');\n const needleLines = oldLf.split('\\n');\n if (needleLines.length > fileLines.length) return undefined;\n const offsets = lineOffsets(fileLines);\n\n const trailing = windowScan(\n fileLines,\n needleLines,\n offsets,\n (a, b) => a.trimEnd() === b.trimEnd(),\n );\n if (trailing.length > 0) return { tier: 'trailing-whitespace', matches: trailing };\n\n const normalizedLen = needleLines.reduce((n, l) => n + l.trim().length, 0);\n if (normalizedLen < MIN_NORMALIZED_NEEDLE_CHARS) return undefined;\n\n const normalized = windowScan(fileLines, needleLines, offsets, (a, b) => a.trim() === b.trim());\n if (normalized.length > 0) return { tier: 'whitespace-normalized', matches: normalized };\n\n return fuzzyScan(fileLines, needleLines, offsets);\n}\n\n/** 1-based line number containing char offset `pos`. */\nfunction lineAt(text: string, pos: number): number {\n let line = 1;\n for (let i = 0; i < pos; i++) {\n if (text.charCodeAt(i) === 0x0a) line++;\n }\n return line;\n}\n\n/** Start char offset of each line in the LF-normalized text. */\nfunction lineOffsets(lines: string[]): number[] {\n const out = new Array<number>(lines.length);\n let pos = 0;\n for (let i = 0; i < lines.length; i++) {\n out[i] = pos;\n pos += (lines[i] as string).length + 1; // +1 for the '\\n'\n }\n return out;\n}\n\nfunction windowToMatch(\n fileLines: string[],\n offsets: number[],\n start: number,\n windowLen: number,\n): LadderMatch {\n const lastLine = start + windowLen - 1;\n return {\n start: offsets[start] as number,\n end: (offsets[lastLine] as number) + (fileLines[lastLine] as string).length,\n startLine: start + 1,\n };\n}\n\n/** Slide a needle-sized window over the file lines with a per-line comparator.\n * Matched windows never overlap (scan resumes after the window). */\nfunction windowScan(\n fileLines: string[],\n needleLines: string[],\n offsets: number[],\n eq: (fileLine: string, needleLine: string) => boolean,\n): LadderMatch[] {\n const n = needleLines.length;\n const out: LadderMatch[] = [];\n for (let i = 0; i + n <= fileLines.length; i++) {\n let all = true;\n for (let j = 0; j < n; j++) {\n if (!eq(fileLines[i + j] as string, needleLines[j] as string)) {\n all = false;\n break;\n }\n }\n if (all) {\n out.push(windowToMatch(fileLines, offsets, i, n));\n i += n - 1; // no overlapping windows\n }\n }\n return out;\n}\n\n/** Block-anchor fuzzy tier: first/last lines trim-exact, interior by\n * Levenshtein similarity. Requires \u22653 needle lines so anchors exist. */\nfunction fuzzyScan(\n fileLines: string[],\n needleLines: string[],\n offsets: number[],\n): LadderResult | undefined {\n const n = needleLines.length;\n if (n < 3) return undefined;\n const firstNeedle = (needleLines[0] as string).trim();\n const lastNeedle = (needleLines[n - 1] as string).trim();\n const needleInterior = needleLines\n .slice(1, -1)\n .map((l) => l.trim())\n .join('\\n');\n if (needleInterior.length > FUZZY_MAX_INTERIOR_CHARS) return undefined;\n\n const candidates: Array<{ match: LadderMatch; score: number }> = [];\n for (let i = 0; i + n <= fileLines.length; i++) {\n if ((fileLines[i] as string).trim() !== firstNeedle) continue;\n if ((fileLines[i + n - 1] as string).trim() !== lastNeedle) continue;\n const windowInterior = fileLines\n .slice(i + 1, i + n - 1)\n .map((l) => l.trim())\n .join('\\n');\n if (windowInterior.length > FUZZY_MAX_INTERIOR_CHARS) continue;\n const score = similarity(needleInterior, windowInterior);\n if (score >= FUZZY_MIN_SIMILARITY) {\n candidates.push({ match: windowToMatch(fileLines, offsets, i, n), score });\n }\n }\n\n if (candidates.length === 0) return undefined;\n candidates.sort((a, b) => b.score - a.score);\n const best = candidates[0] as { match: LadderMatch; score: number };\n const runnerUp = candidates[1];\n if (runnerUp && best.score - runnerUp.score < FUZZY_AMBIGUITY_MARGIN) {\n return {\n tier: 'fuzzy',\n matches: candidates.map((c) => c.match),\n score: best.score,\n ambiguous: true,\n };\n }\n return { tier: 'fuzzy', matches: [best.match], score: best.score };\n}\n\n/** Normalized similarity in [0, 1]: 1 \u2212 levenshtein / max-length. */\nexport function similarity(a: string, b: string): number {\n if (a === b) return 1;\n const max = Math.max(a.length, b.length);\n if (max === 0) return 1;\n // Quick reject: a length gap larger than the allowed edit budget can\n // never reach the threshold \u2014 skip the O(n\u00B7m) DP entirely.\n if (Math.abs(a.length - b.length) / max > 1 - FUZZY_MIN_SIMILARITY + 0.05) {\n return 1 - Math.abs(a.length - b.length) / max;\n }\n return 1 - levenshtein(a, b) / max;\n}\n\n/** Two-row Levenshtein distance. */\nfunction levenshtein(a: string, b: string): number {\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n let prev = new Array<number>(b.length + 1);\n let cur = new Array<number>(b.length + 1);\n for (let j = 0; j <= b.length; j++) prev[j] = j;\n for (let i = 1; i <= a.length; i++) {\n cur[0] = i;\n const ca = a.charCodeAt(i - 1);\n for (let j = 1; j <= b.length; j++) {\n const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;\n cur[j] = Math.min(\n (prev[j] as number) + 1,\n (cur[j - 1] as number) + 1,\n (prev[j - 1] as number) + cost,\n );\n }\n [prev, cur] = [cur, prev];\n }\n return prev[b.length] as number;\n}\n\n/** Leading whitespace (spaces/tabs) of the first non-empty line, or ''. */\nexport function firstLineIndent(text: string): string {\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue;\n return (/^[ \\t]*/.exec(line) as RegExpExecArray)[0];\n }\n return '';\n}\n\n/**\n * Shift `newLf` from the needle's indentation to the matched block's actual\n * indentation. Only clean prefix relationships are adjusted (purely adding\n * or removing leading whitespace); a tabs-vs-spaces mismatch is left alone\n * rather than guessed at \u2014 the diff in the tool output makes the result\n * visible either way.\n */\nexport function adjustIndent(\n newLf: string,\n fromIndent: string,\n toIndent: string,\n): { text: string; adjusted: boolean } {\n if (fromIndent === toIndent) return { text: newLf, adjusted: false };\n const lines = newLf.split('\\n');\n if (toIndent.startsWith(fromIndent)) {\n const extra = toIndent.slice(fromIndent.length);\n return {\n text: lines.map((l) => (l.trim() === '' ? l : extra + l)).join('\\n'),\n adjusted: true,\n };\n }\n if (fromIndent.startsWith(toIndent)) {\n const remove = fromIndent.slice(toIndent.length);\n return {\n text: lines.map((l) => (l.startsWith(remove) ? l.slice(remove.length) : l)).join('\\n'),\n adjusted: true,\n };\n }\n return { text: newLf, adjusted: false };\n}\n\n/**\n * Best-effort locator for the no-match error: find the window whose trimmed\n * lines are most similar to the needle (cheap common-prefix scoring \u2014 this\n * runs on the failure path where the needle by definition doesn't match, so\n * precision matters less than never being slow). Returns the 1-based line\n * and a short snippet the model can use to correct itself without re-reading.\n */\nexport function nearestMatchHint(\n fileLf: string,\n oldLf: string,\n): { line: number; snippet: string } | undefined {\n const fileLines = fileLf.split('\\n');\n const needleLines = oldLf.split('\\n');\n if (needleLines.length > fileLines.length) return undefined;\n\n const needleTrimmed = needleLines.map((l) => l.trim());\n let bestScore = 0;\n let bestStart = -1;\n for (let i = 0; i + needleLines.length <= fileLines.length; i++) {\n let sum = 0;\n for (let j = 0; j < needleLines.length; j++) {\n sum += prefixSimilarity(needleTrimmed[j] as string, (fileLines[i + j] as string).trim());\n }\n const score = sum / needleLines.length;\n if (score > bestScore) {\n bestScore = score;\n bestStart = i;\n }\n }\n if (bestStart === -1 || bestScore < 0.5) return undefined;\n\n const snippet = fileLines\n .slice(bestStart, bestStart + Math.min(3, needleLines.length))\n .map((l) => (l.length > 120 ? `${l.slice(0, 120)}\u2026` : l))\n .join('\\n');\n return { line: bestStart + 1, snippet };\n}\n\n/** Cheap similarity proxy: shared prefix length over max length. */\nfunction prefixSimilarity(a: string, b: string): number {\n if (a === b) return 1;\n const max = Math.max(a.length, b.length);\n if (max === 0) return 1;\n let p = 0;\n const lim = Math.min(a.length, b.length);\n while (p < lim && a.charCodeAt(p) === b.charCodeAt(p)) p++;\n return p / max;\n}\n", "/**\n * Post-edit syntax validation for `edit` and `write`.\n *\n * After a mutation lands on disk, the new content is parsed (TS/JS via the\n * TypeScript compiler's parser, JSON via JSON.parse with a JSONC fallback)\n * and any parse errors are surfaced in the tool output. The edit is NOT\n * rolled back \u2014 reverting would desynchronize the model's picture of the\n * file \u2014 but the errors come back in the same turn so the model fixes the\n * breakage before the user ever opens a broken file.\n *\n * The check is purely syntactic (no type checking, no project program), so\n * it costs single-digit milliseconds per file. Semantic type errors remain\n * the `type-gate` plugin's job.\n */\nimport * as path from 'node:path';\n\nconst TS_LIKE = new Set(['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs']);\n/** Skip pathologically large files \u2014 the model never writes these in one go. */\nconst MAX_CHECK_CHARS = 1_500_000;\n/** Cap reported errors so a mangled file doesn't flood the context. */\nconst MAX_ERRORS = 5;\n\ntype Ts = typeof import('@typescript/typescript6');\n\nlet tsLoad: Promise<Ts | null> | null = null;\n/** Lazy-load the TypeScript compiler (already a dependency via the codebase\n * indexer) so tools that never touch TS files don't pay its import cost. */\nfunction loadTypescript(): Promise<Ts | null> {\n tsLoad ??= import('@typescript/typescript6').then(\n (m) => ((m as unknown as { default?: Ts }).default ?? m) as Ts,\n () => null,\n );\n return tsLoad;\n}\n\nexport interface SyntaxCheckResult {\n /** Parse errors in the new content (capped at {@link MAX_ERRORS}). */\n errors: string[];\n /** True when the pre-edit content already failed to parse \u2014 the edit did\n * not introduce the breakage (though it didn't fix it either). */\n preExisting: boolean;\n}\n\n/**\n * Check `content` for syntax errors based on the file extension.\n * Returns `undefined` when the file type is not checkable (or the checker\n * is unavailable) and a result with an empty `errors` array when clean.\n * Pass `previousContent` to distinguish newly-introduced errors from\n * pre-existing ones.\n */\nexport async function checkSyntax(\n filePath: string,\n content: string,\n previousContent?: string,\n): Promise<SyntaxCheckResult | undefined> {\n const errors = await parseErrors(filePath, content);\n if (errors === undefined) return undefined;\n if (errors.length === 0) return { errors, preExisting: false };\n let preExisting = false;\n if (previousContent !== undefined) {\n const prevErrors = await parseErrors(filePath, previousContent);\n preExisting = prevErrors !== undefined && prevErrors.length > 0;\n }\n return { errors, preExisting };\n}\n\n/** Files that are conventionally JSONC (comments + trailing commas allowed). */\nfunction isJsoncFile(filePath: string): boolean {\n const base = path.basename(filePath).toLowerCase();\n if (base.endsWith('.jsonc')) return true;\n if (/^(tsconfig|jsconfig)([.-].*)?\\.json$/.test(base)) return true;\n const dir = path.basename(path.dirname(filePath)).toLowerCase();\n return dir === '.vscode';\n}\n\nasync function parseErrors(filePath: string, content: string): Promise<string[] | undefined> {\n if (content.length > MAX_CHECK_CHARS) return undefined;\n const ext = path.extname(filePath).toLowerCase();\n\n if (ext === '.json' || ext === '.jsonc') {\n try {\n JSON.parse(content);\n return [];\n } catch (err) {\n // Known-JSONC files (tsconfig.json, VS Code settings, *.jsonc) accept\n // comments and trailing commas \u2014 re-parse with the TS config parser\n // before declaring them broken. Plain .json stays strict: a trailing\n // comma there IS an error the model should fix.\n if (isJsoncFile(filePath)) {\n const ts = await loadTypescript();\n if (ts) {\n const jsonc = ts.parseConfigFileTextToJson(filePath, content);\n if (!jsonc.error) return [];\n return [formatDiag(ts, jsonc.error, content)];\n }\n }\n return [`JSON parse error: ${(err as Error).message}`];\n }\n }\n\n if (!TS_LIKE.has(ext)) return undefined;\n const ts = await loadTypescript();\n if (!ts) return undefined;\n\n const scriptKind =\n ext === '.tsx'\n ? ts.ScriptKind.TSX\n : ext === '.ts' || ext === '.mts' || ext === '.cts'\n ? ts.ScriptKind.TS\n : // Plain JS may legitimately contain JSX; the JSX grammar is a\n // superset for untyped code, so parsing .js/.jsx as JSX avoids\n // false positives on React files.\n ts.ScriptKind.JSX;\n\n const sourceFile = ts.createSourceFile(\n path.basename(filePath),\n content,\n ts.ScriptTarget.Latest,\n /* setParentNodes */ false,\n scriptKind,\n );\n // parseDiagnostics is not in the public .d.ts but has been the stable\n // home of the parser's syntactic diagnostics for a decade (the public\n // alternative, program.getSyntacticDiagnostics, needs a full Program).\n const diags =\n (sourceFile as unknown as {\n parseDiagnostics?: import('@typescript/typescript6').Diagnostic[];\n })\n .parseDiagnostics ?? [];\n return diags.slice(0, MAX_ERRORS).map((d) => formatDiag(ts, d, content, sourceFile));\n}\n\nfunction formatDiag(\n ts: Ts,\n diag: import('@typescript/typescript6').Diagnostic,\n content: string,\n sourceFile?: import('@typescript/typescript6').SourceFile,\n): string {\n const message = ts.flattenDiagnosticMessageText(diag.messageText, ' ');\n if (diag.start === undefined) return message;\n let line: number;\n if (sourceFile) {\n line = sourceFile.getLineAndCharacterOfPosition(diag.start).line + 1;\n } else {\n line = 1;\n for (let i = 0; i < diag.start && i < content.length; i++) {\n if (content.charCodeAt(i) === 0x0a) line++;\n }\n }\n return `line ${line}: ${message}`;\n}\n", "import { createHash } from 'node:crypto';\nimport * as fsp from 'node:fs/promises';\nimport * as path from 'node:path';\nimport * as Core from '@wrongstack/core';\nimport type { Context } from '@wrongstack/core';\n\n/**\n * sha-256 hex of a UTF-8 string. Used by the file tools to record a content\n * hash alongside the mtime in `ctx.recordRead` \u2014 the hash is the authoritative\n * staleness arbiter for `edit` (mtime has a 2 s tolerance window on Windows).\n */\nexport function sha256hex(content: string): string {\n return createHash('sha256').update(content, 'utf8').digest('hex');\n}\n/** Detected package manager for a project directory. */\nexport type PackageManager = 'pnpm' | 'yarn' | 'npm';\n\n/**\n * Detect the project's package manager by inspecting lockfiles in `cwd`.\n * Order: pnpm \u2192 yarn \u2192 npm (default). Missing or unreadable directories fall\n * back to `npm` rather than throwing, so a `safeResolve`-checked cwd that\n * happens to be empty never aborts the tool.\n */\nexport async function detectPackageManager(cwd: string): Promise<PackageManager> {\n const { stat } = await import('node:fs/promises');\n try {\n await stat(`${cwd}/pnpm-lock.yaml`);\n return 'pnpm';\n } catch {\n /* not pnpm */\n }\n try {\n await stat(`${cwd}/yarn.lock`);\n return 'yarn';\n } catch {\n /* not yarn */\n }\n return 'npm';\n}\n\nexport function resolvePath(input: string, ctx: Context): string {\n return path.isAbsolute(input) ? path.normalize(input) : path.resolve(ctx.workingDir ?? ctx.cwd, input);\n}\n\n/**\n * Roots every file tool may always reach, even in restricted mode: the\n * project root and the user-global `~/.wrongstack` directory (config, memory,\n * sessions, skills). `~/.wrongstack` honors the `WRONGSTACK_HOME` override.\n */\nfunction allowedRoots(ctx: Context): string[] {\n return [path.resolve(ctx.projectRoot), path.resolve(Core.wstackGlobalRoot())];\n}\n\n/** True if `target` is `root` itself or nested inside any of `roots`. */\nfunction isInsideAny(target: string, roots: string[]): boolean {\n return roots.some((root) => {\n const rel = path.relative(root, target);\n return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));\n });\n}\n\nexport function ensureInsideRoot(absPath: string, ctx: Context): string {\n const target = path.resolve(absPath);\n // Unrestricted filesystem access: skip the project-root containment check.\n if (ctx.allowOutsideProjectRoot) return target;\n if (isInsideAny(target, allowedRoots(ctx))) return target;\n throw new Error(`Path \"${absPath}\" is outside project root \"${path.resolve(ctx.projectRoot)}\"`);\n}\n\nexport function safeResolve(input: string, ctx: Context): string {\n return ensureInsideRoot(resolvePath(input, ctx), ctx);\n}\n\n/**\n * Defense against in-root\u2192out-of-root symlink escape (CWE-59). `safeResolve`\n * only does a syntactic `../` check, so a symlink that lives *inside* the\n * project root but points outside still passes it. This resolves the path\n * through `fs.realpath` and re-verifies containment against the realpath of\n * the project root (comparing like-for-like, since the root itself may be a\n * symlink \u2014 macOS `/var`\u2192`/private/var`, Windows 8.3 short names). For a path\n * that does not exist yet (e.g. a `write` to a new file) the nearest existing\n * ancestor directory is checked instead. Throws if the real target escapes.\n *\n * Mirrors the per-file guard already used in `replace.ts`/`grep.ts`; applied\n * to single-file `read`/`edit`/`write` it throws (rather than skips) because\n * the caller named exactly one file.\n */\nexport async function assertRealInsideRoot(absPath: string, ctx: Context): Promise<void> {\n // Unrestricted filesystem access: no symlink-escape check to perform.\n if (ctx.allowOutsideProjectRoot) return;\n // Compare like-for-like against the realpath of each always-allowed root\n // (project root + ~/.wrongstack), since a root may itself be a symlink.\n const realRoots = await Promise.all(\n allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path.resolve(r))),\n );\n let probe = absPath;\n for (;;) {\n let real: string;\n try {\n real = await fsp.realpath(probe);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') {\n const parent = path.dirname(probe);\n if (parent === probe) return; // reached fs root without escaping\n probe = parent;\n continue;\n }\n throw err;\n }\n if (isInsideAny(real, realRoots)) return;\n throw new Error(\n `Path \"${absPath}\" resolves through a symlink outside project root \"${realRoots[0]}\"`,\n );\n }\n}\n\n/** `safeResolve` + symlink realpath containment check. Async. */\nexport async function safeResolveReal(input: string, ctx: Context): Promise<string> {\n const abs = safeResolve(input, ctx);\n await assertRealInsideRoot(abs, ctx);\n return abs;\n}\n\nexport function truncateMiddle(s: string, max: number): string {\n if (Buffer.byteLength(s, 'utf8') <= max) return s;\n const half = Math.floor(max / 2);\n return (\n s.slice(0, half) +\n `\\n\u2026[truncated ${Buffer.byteLength(s, 'utf8') - max} bytes from middle]\u2026\\n` +\n s.slice(-half)\n );\n}\n\nexport function isBinaryBuffer(buf: Buffer): boolean {\n const len = Math.min(buf.length, 8192);\n for (let i = 0; i < len; i++) {\n if (buf[i] === 0) return true;\n }\n return false;\n}\n\n// \u2500\u2500\u2500 Command-output normalization (token-saving) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Raw process output is full of tokens the model gains nothing from: ANSI\n// escapes, carriage-return progress spam, runs of identical warning lines, and\n// huge tails of build noise. These helpers strip that noise before the output\n// reaches the LLM. They are scoped to COMMAND tools (bash/git/exec and the\n// _spawn-stream consumers) \u2014 never applied to structured/code outputs.\n\n/** Unified byte cap for all command tool output fed to the model. */\nexport const COMMAND_OUTPUT_MAX_BYTES = 32_768;\n\n/** Runs of >= this many identical consecutive lines are collapsed. */\nconst REPEAT_RUN_THRESHOLD = 3;\n\n/**\n * Collapse carriage-return overwrites the way a terminal would: `\\r\\n` becomes\n * `\\n`, and a bare `\\r` (progress redraw) keeps only the text after the LAST\n * `\\r` on its physical line. Without this, a single progress bar that redraws\n * 200 times explodes into 200 lines.\n */\nexport function collapseCarriageReturns(text: string): string {\n const lf = text.replace(/\\r\\n/g, '\\n');\n if (!lf.includes('\\r')) return lf;\n return lf\n .split('\\n')\n .map((line) => (line.includes('\\r') ? line.slice(line.lastIndexOf('\\r') + 1) : line))\n .join('\\n');\n}\n\n/**\n * Collapse a run of `minRun`+ identical consecutive lines into the line once\n * plus a marker. Consecutive-only \u2014 it never reorders or dedups non-adjacent\n * lines, so diffs/source stay intact.\n */\nexport function collapseConsecutiveDuplicates(text: string, minRun = REPEAT_RUN_THRESHOLD): string {\n const lines = text.split('\\n');\n const out: string[] = [];\n let i = 0;\n while (i < lines.length) {\n let j = i + 1;\n while (j < lines.length && lines[j] === lines[i]) j++;\n const run = j - i;\n if (run >= minRun) {\n out.push(lines[i]!, `\u2026 \u27E8repeated ${run}\u00D7\u27E9`);\n } else {\n for (let k = i; k < j; k++) out.push(lines[k]!);\n }\n i = j;\n }\n return out.join('\\n');\n}\n\n/** Largest prefix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeHeadBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(0, mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(0, lo);\n}\n\n/** Largest suffix of `s` whose UTF-8 byte length is <= `maxBytes`. */\nfunction takeTailBytes(s: string, maxBytes: number): string {\n if (maxBytes <= 0) return '';\n /* v8 ignore next -- only caller (truncateHeadTail) passes a budget smaller than s; defensive. */\n if (Buffer.byteLength(s, 'utf8') <= maxBytes) return s;\n let lo = 0;\n let hi = s.length;\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2);\n if (Buffer.byteLength(s.slice(s.length - mid), 'utf8') <= maxBytes) lo = mid;\n else hi = mid - 1;\n }\n return s.slice(s.length - lo);\n}\n\n/**\n * Truncate to `maxBytes` keeping BOTH ends \u2014 the head (what ran / early context)\n * and the tail (errors and summaries usually land last), biased ~45/55 toward\n * the tail. The result never exceeds `maxBytes`.\n */\nexport function truncateHeadTail(s: string, maxBytes: number): string {\n const total = Buffer.byteLength(s, 'utf8');\n if (total <= maxBytes) return s;\n // Reserve a fixed allowance for the marker so the final string can't exceed\n // the cap even though the dropped-byte count's digit width varies.\n const MARKER_RESERVE = 64;\n const avail = Math.max(0, maxBytes - MARKER_RESERVE);\n const headBudget = Math.floor(avail * 0.45);\n const head = takeHeadBytes(s, headBudget);\n const tail = takeTailBytes(s, avail - Buffer.byteLength(head, 'utf8'));\n const kept = Buffer.byteLength(head, 'utf8') + Buffer.byteLength(tail, 'utf8');\n return `${head}\\n\u2026[truncated ${total - kept} bytes]\u2026\\n${tail}`;\n}\n\n/**\n * Full token-saving pipeline for command tool output: strip ANSI \u2192 collapse\n * carriage-return progress \u2192 trim trailing whitespace \u2192 collapse identical\n * consecutive lines \u2192 squeeze blank-line runs \u2192 head+tail truncate to the cap.\n */\nexport function normalizeCommandOutput(\n raw: string,\n opts: { maxBytes?: number | undefined } = {},\n): string {\n if (!raw) return raw;\n let text = Core.stripAnsi(raw);\n text = collapseCarriageReturns(text);\n text = text.replace(/[ \\t]+$/gm, ''); // trailing whitespace per line\n text = collapseConsecutiveDuplicates(text);\n text = text.replace(/\\n{3,}/g, '\\n\\n'); // >=2 blank lines \u2192 1\n return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["ts", "path", "stat"]
7
+ }
package/dist/exec.d.ts CHANGED
@@ -1,2 +1,67 @@
1
- import '@wrongstack/core';
2
- export { c as configureDangerBypass, b as configureExecPolicy, e as execTool, g as getDangerBypass, f as getExecAllowlist, i as isExecCommandAllowed, r as resetDangerBypass, h as resetExecPolicy } from './exec-2OKoT6tk.js';
1
+ import type { Tool } from '@wrongstack/core';
2
+ import { type DangerAssessment } from './_danger-detect.js';
3
+ /**
4
+ * Apply the configured exec command policy. Recomputes the effective allowlist
5
+ * as `DEFAULT ∪ allow − deny`. Call once at boot from
6
+ * `config.tools.exec.{allow,deny}`. Idempotent (always rebuilt from defaults).
7
+ *
8
+ * SECURITY: `allow` must originate from TRUSTED config only — the config loader
9
+ * strips `tools.exec.allow` from the untrusted in-project repo config before it
10
+ * reaches here. `deny` is safe from any source (it only narrows).
11
+ */
12
+ export declare function configureExecPolicy(opts?: {
13
+ allow?: readonly string[] | undefined;
14
+ deny?: readonly string[] | undefined;
15
+ }): void;
16
+ /** Reset the exec allowlist to the built-in defaults (tests / re-init). */
17
+ export declare function resetExecPolicy(): void;
18
+ /**
19
+ * Apply the configured danger-bypass policy. Each id in `bypass` is
20
+ * added to the effective skip set; duplicates are fine. Idempotent.
21
+ *
22
+ * Call once at boot from `config.tools.exec.danger.bypass`.
23
+ */
24
+ export declare function configureDangerBypass(opts?: {
25
+ bypass?: readonly string[] | undefined;
26
+ }): void;
27
+ /** Reset the danger-bypass set to empty (tests / re-init). */
28
+ export declare function resetDangerBypass(): void;
29
+ /**
30
+ * Read-only view of the active bypass set. `detectDanger()` takes a
31
+ * `bypass` argument directly, so consumers should prefer passing this
32
+ * rather than reading the set and matching themselves.
33
+ */
34
+ export declare function getDangerBypass(): ReadonlySet<string>;
35
+ /** Whether `cmd` is currently in the effective exec allowlist. */
36
+ export declare function isExecCommandAllowed(cmd: string): boolean;
37
+ /** Snapshot of the effective allowlist (sorted) — for tests / diagnostics. */
38
+ export declare function getExecAllowlist(): string[];
39
+ interface ExecInput {
40
+ command: string;
41
+ args?: string[] | undefined;
42
+ cwd?: string | undefined;
43
+ timeout?: number | undefined;
44
+ }
45
+ interface ExecOutput {
46
+ command: string;
47
+ args: string[];
48
+ stdout: string;
49
+ stderr: string;
50
+ exitCode: number;
51
+ truncated: boolean;
52
+ allowed: boolean;
53
+ /**
54
+ * Heuristic danger assessment of the (cmd, args) pair. Populated for every
55
+ * call (not just blocked ones) so the UI/TUI can render a banner when the
56
+ * level is 'caution' or 'destructive'. See `_danger-detect.ts` for the
57
+ * rule set.
58
+ *
59
+ * Pre-execution error returns (allowlist miss, circuit breaker, etc.)
60
+ * report `level: 'safe'` because the command never actually ran; the UI
61
+ * should surface the error separately and not also a danger warning.
62
+ */
63
+ danger: DangerAssessment;
64
+ }
65
+ export declare const execTool: Tool<ExecInput, ExecOutput>;
66
+ export {};
67
+ //# sourceMappingURL=exec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.d.ts","sourceRoot":"","sources":["../src/exec.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAU7C,OAAO,EAAgB,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA8J1E;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CAAO,GAAG,IAAI,CAQpI;AAED,2EAA2E;AAC3E,wBAAgB,eAAe,IAAI,IAAI,CAEtC;AAkBD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;CAAO,GAAG,IAAI,CAOjG;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED;;;;GAIG;AACH,wBAAgB,eAAe,IAAI,WAAW,CAAC,MAAM,CAAC,CAErD;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEzD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C;AA4GD,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,GAAG,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC9B;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB;;;;;;;;;OASG;IACH,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAID,eAAO,MAAM,QAAQ,EAAE,IAAI,CAAC,SAAS,EAAE,UAAU,CA0IhD,CAAC"}
package/dist/exec.js CHANGED
@@ -1,19 +1,20 @@
1
- import { spawn } from 'node:child_process';
2
- import { toErrorMessage } from '@wrongstack/core/utils/error';
3
- import * as Core from '@wrongstack/core';
4
- import { buildChildEnv, expectDefined, wstackGlobalRoot } from '@wrongstack/core';
5
- import * as fs from 'node:fs';
6
- import { mkdirSync, createWriteStream } from 'node:fs';
7
- import * as fsp from 'node:fs/promises';
8
- import * as path2 from 'node:path';
9
- import * as os from 'node:os';
10
-
11
1
  // src/exec.ts
2
+ import { spawn as spawn2 } from "node:child_process";
3
+ import { toErrorMessage } from "@wrongstack/core/utils/error";
4
+
5
+ // src/_env.ts
6
+ import { buildChildEnv } from "@wrongstack/core";
7
+
8
+ // src/_output-spool.ts
9
+ import { createWriteStream, mkdirSync } from "node:fs";
10
+ import * as fsp from "node:fs/promises";
11
+ import * as path from "node:path";
12
+ import { wstackGlobalRoot } from "@wrongstack/core";
12
13
  var SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1e3;
13
14
  var SPOOL_WRITE_HWM_BYTES = 4 * 1024 * 1024;
14
15
  var sweepStarted = false;
15
16
  function toolOutputDir() {
16
- return path2.join(wstackGlobalRoot(), "tool-output");
17
+ return path.join(wstackGlobalRoot(), "tool-output");
17
18
  }
18
19
  function sweepOldSpoolFiles(dir) {
19
20
  if (sweepStarted) return;
@@ -23,7 +24,7 @@ function sweepOldSpoolFiles(dir) {
23
24
  const now = Date.now();
24
25
  for (const name of await fsp.readdir(dir)) {
25
26
  if (!name.endsWith(".log")) continue;
26
- const p = path2.join(dir, name);
27
+ const p = path.join(dir, name);
27
28
  try {
28
29
  const st = await fsp.stat(p);
29
30
  if (now - st.mtimeMs > SPOOL_RETENTION_MS) await fsp.unlink(p);
@@ -40,7 +41,7 @@ function spoolNote(info) {
40
41
  [output truncated \u2014 full ${info.bytes} bytes at ${info.path}${dropped}; read/grep that file selectively instead of re-running with more output]`;
41
42
  }
42
43
  function createOutputSpool(opts) {
43
- const threshold = opts.thresholdBytes;
44
+ const threshold = opts.thresholdBytes ?? 32768;
44
45
  const safeTool = opts.tool.replace(/[^a-zA-Z0-9._-]+/g, "_").slice(0, 40) || "tool";
45
46
  let head = "";
46
47
  let headBytes = 0;
@@ -58,7 +59,7 @@ function createOutputSpool(opts) {
58
59
  sweepOldSpoolFiles(dir);
59
60
  const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
60
61
  const rand = Math.random().toString(36).slice(2, 6);
61
- filePath = path2.join(dir, `${stamp}-${safeTool}-${rand}.log`);
62
+ filePath = path.join(dir, `${stamp}-${safeTool}-${rand}.log`);
62
63
  stream = createWriteStream(filePath, { flags: "w", encoding: "utf8" });
63
64
  stream.on("error", () => {
64
65
  failed = true;
@@ -110,6 +111,11 @@ function createOutputSpool(opts) {
110
111
  }
111
112
  };
112
113
  }
114
+
115
+ // src/_util.ts
116
+ import * as fsp2 from "node:fs/promises";
117
+ import * as path2 from "node:path";
118
+ import * as Core from "@wrongstack/core";
113
119
  function resolvePath(input, ctx) {
114
120
  return path2.isAbsolute(input) ? path2.normalize(input) : path2.resolve(ctx.workingDir ?? ctx.cwd, input);
115
121
  }
@@ -134,13 +140,13 @@ function safeResolve(input, ctx) {
134
140
  async function assertRealInsideRoot(absPath, ctx) {
135
141
  if (ctx.allowOutsideProjectRoot) return;
136
142
  const realRoots = await Promise.all(
137
- allowedRoots(ctx).map((r) => fsp.realpath(r).catch(() => path2.resolve(r)))
143
+ allowedRoots(ctx).map((r) => fsp2.realpath(r).catch(() => path2.resolve(r)))
138
144
  );
139
145
  let probe = absPath;
140
146
  for (; ; ) {
141
147
  let real;
142
148
  try {
143
- real = await fsp.realpath(probe);
149
+ real = await fsp2.realpath(probe);
144
150
  } catch (err) {
145
151
  if (err.code === "ENOENT") {
146
152
  const parent = path2.dirname(probe);
@@ -232,6 +238,10 @@ function normalizeCommandOutput(raw, opts = {}) {
232
238
  return truncateHeadTail(text, opts.maxBytes ?? COMMAND_OUTPUT_MAX_BYTES);
233
239
  }
234
240
 
241
+ // src/process-registry.ts
242
+ import { spawn } from "node:child_process";
243
+ import * as os from "node:os";
244
+
235
245
  // src/circuit-breaker.ts
236
246
  var DEFAULT_MAX_CONSECUTIVE_FAILURES = 5;
237
247
  var DEFAULT_SLOW_CALL_THRESHOLD_MS = 18e4;
@@ -424,7 +434,8 @@ var CircuitBreaker = class {
424
434
  }
425
435
  };
426
436
 
427
- // src/process-registry.ts
437
+ // src/_redact-command.ts
438
+ import { expectDefined } from "@wrongstack/core";
428
439
  var SENSITIVE_FLAG_PATTERNS = [
429
440
  // --flag=value or --flag "value" (value captured up to next space or comma)
430
441
  /--(?:token|password|passwd|pwd|secret|api[-_]?key|api[-_]?secret|auth|credential|private[-_]?key|access[-_]?key|github[-_]?token|gh[-_]?token|bearer|jwt|oauth|pin|pincode|passphrase|access[-_]?token)(?:[=\s,][^\s]*)?/gi,
@@ -454,6 +465,8 @@ function redactCommand(cmd) {
454
465
  }
455
466
  return result;
456
467
  }
468
+
469
+ // src/process-registry.ts
457
470
  var DEFAULT_GRACE_MS = 2e3;
458
471
  var WIN32_TASKKILL_TIMEOUT_MS = 5e3;
459
472
  function killWin32Tree(pid, opts = {}) {
@@ -723,7 +736,8 @@ var ProcessRegistryImpl = class {
723
736
  if (liveRealChild && killWin32Tree(pid, {
724
737
  timeoutMs: Math.max(graceMs, WIN32_TASKKILL_TIMEOUT_MS),
725
738
  onSettled: directFallback
726
- })) ; else {
739
+ })) {
740
+ } else {
727
741
  try {
728
742
  p.child.kill(force ? "SIGKILL" : "SIGTERM");
729
743
  } catch {
@@ -810,15 +824,19 @@ function getProcessRegistry() {
810
824
  }
811
825
  return _registry;
812
826
  }
827
+
828
+ // src/_win32-resolve.ts
829
+ import * as fs from "node:fs";
830
+ import * as path3 from "node:path";
813
831
  function resolveWin32Command(cmd) {
814
832
  if (process.platform !== "win32") return cmd;
815
- if (cmd.includes("/") || cmd.includes("\\") || path2.extname(cmd.replace(/\//g, "\\"))) {
833
+ if (cmd.includes("/") || cmd.includes("\\") || path3.extname(cmd.replace(/\//g, "\\"))) {
816
834
  return cmd;
817
835
  }
818
836
  const pathext = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC").toLowerCase().split(";");
819
- const pathDirs = (process.env["PATH"] ?? "").split(path2.delimiter);
837
+ const pathDirs = (process.env["PATH"] ?? "").split(path3.delimiter);
820
838
  for (const dir of pathDirs) {
821
- const base = path2.join(dir, cmd);
839
+ const base = path3.join(dir, cmd);
822
840
  for (const ext of pathext) {
823
841
  const full = `${base}${ext}`;
824
842
  try {
@@ -1805,7 +1823,7 @@ var BLOCKED_ARG_PATTERNS = {
1805
1823
  // and glob patterns that could expand to dangerous targets.
1806
1824
  // `rm -rf ./src/*` expands to project files; `rm -rf ../../` escapes upward;
1807
1825
  // `rm -rf /*` targets the filesystem root. All are blocked.
1808
- rm: [/^\//, /^~\//, /^~$/, /^\.$/, /^\.\.$/, /\*$/, /\/$/, /\/\*$/, /\.\//],
1826
+ rm: [/^\//, /^[A-Za-z]:[\\/]/, /^~\//, /^~$/, /^\.$/, /^\.\.$/, /\*$/, /\/$/, /\/\*$/, /\.\//],
1809
1827
  // npm/pnpm subcommands are checked separately below. Matching every arg here
1810
1828
  // over-blocked normal dev flows such as `pnpm vitest run ...`.
1811
1829
  npm: [],
@@ -1992,7 +2010,7 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
1992
2010
  const spawnArgs = shim?.args ?? args;
1993
2011
  let child;
1994
2012
  try {
1995
- child = spawn(spawnCmd, spawnArgs, {
2013
+ child = spawn2(spawnCmd, spawnArgs, {
1996
2014
  cwd,
1997
2015
  env: buildChildEnv(sessionId),
1998
2016
  stdio: ["ignore", "pipe", "pipe"],
@@ -2084,7 +2102,14 @@ function runCommand(cmd, args, cwd, timeout, signal, sessionId, danger) {
2084
2102
  });
2085
2103
  });
2086
2104
  }
2087
-
2088
- export { configureDangerBypass, configureExecPolicy, execTool, getDangerBypass, getExecAllowlist, isExecCommandAllowed, resetDangerBypass, resetExecPolicy };
2105
+ export {
2106
+ configureDangerBypass,
2107
+ configureExecPolicy,
2108
+ execTool,
2109
+ getDangerBypass,
2110
+ getExecAllowlist,
2111
+ isExecCommandAllowed,
2112
+ resetDangerBypass,
2113
+ resetExecPolicy
2114
+ };
2089
2115
  //# sourceMappingURL=exec.js.map
2090
- //# sourceMappingURL=exec.js.map