mixdog 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,603 @@
1
+ // Edit input normalization helpers — extracted from builtin.mjs so the
2
+ // matching tiers (curly-quote fold / NFC-fold) can be tested in isolation
3
+ // and audited without scrolling through the full tool dispatcher.
4
+ //
5
+ import { findCrlfNormalisedMatches } from './builtin/edit-match-utils.mjs';
6
+ //
7
+ // HARD RULE — tier ordering + ambiguity gates. Invariant-safe tiers run
8
+ // first (byte-exact, curly-quote fold, NFC-fold, CRLF-fold). Last-resort Claude-Code
9
+ // parity tiers (rstrip-fold, indent-fold, eol-fold) are lossy views but
10
+ // safe: each runs only after invariant-safe tiers (including CRLF-fold) miss, rejects >1 folded
11
+ // hit with code 9 (diagnoseFoldTierAmbiguity), and always replaces a real
12
+ // content.substring slice — never synthesised folded bytes. Still removed:
13
+ // case/dash/fullwidth/Unicode-space folding and unconstrained indent-shift
14
+ // (whole-block re-indent), which can alias unrelated regions.
15
+ //
16
+ // All helpers preserve the same byte-exact invariant: matchers return a
17
+ // real substring of `content` (never a synthesised one), and length-
18
+ // preserving folds keep input.length so an indexOf hit re-indexes the
19
+ // original bytes. See per-function comments for stage-specific guards.
20
+
21
+ // CC parity: curly-quote tolerant match. Models can't reliably emit `‘`
22
+ // / `’` / `“` / `”`, so when a file has curly quotes but
23
+ // old_string uses straight (or vice versa) the literal indexOf misses.
24
+ // This is the ONLY fold retained in this stage and it is invariant-safe:
25
+ // a curly quote and its straight counterpart are the same character in a
26
+ // different typographic encoding. Each mapped codepoint is a single
27
+ // UTF-16 code unit (BMP only, no surrogate pairs), so an `indexOf` hit in
28
+ // the folded view re-indexes the original file substring without
29
+ // arithmetic adjustment — the caller can
30
+ // `content.substring(idx, idx + searchStr.length)` and get a byte-exact
31
+ // slice back.
32
+ //
33
+ // Folded codepoints:
34
+ // Single → "'" : U+2018-U+201B (curly + low/high-reversed-9).
35
+ // Double → '"' : U+201C-U+201F (curly + low/high-reversed-9).
36
+ //
37
+ // Deliberately NOT folded (would be heuristic-risky, not invariant-safe):
38
+ // case, dash/minus variants, fullwidth forms, and Unicode whitespace —
39
+ // each can map text that is genuinely different to the same view and hit
40
+ // the wrong location. NFC / NFD is handled in its own invariant-safe tier
41
+ // (nfcFoldMatch). ZWSP / BOM remain hint-only.
42
+ export const MATCH_FOLD_RE = /[‘-‟]/g;
43
+ export function normalizeForMatch(s) {
44
+ return String(s).replace(MATCH_FOLD_RE, (c) => {
45
+ const code = c.charCodeAt(0);
46
+ if (code >= 0x2018 && code <= 0x201B) return "'";
47
+ return '"';
48
+ });
49
+ }
50
+
51
+ // NFC/NFD fold helper. Returns the byte-exact slice from `content` whose
52
+ // normalised form equals the normalised search, or null if no unique
53
+ // safe match exists.
54
+ //
55
+ // Why NFD (not NFC) is the comparison medium: NFC composition is NOT
56
+ // char-by-char — multiple input chars can collapse into one (e.g. Hangul
57
+ // `각` → `각`). That breaks any attempt to map an
58
+ // index in nfcContent back to original char positions via per-char NFC
59
+ // length accumulation. NFD decomposition IS char-by-char additive: each
60
+ // input char produces its own NFD output independent of neighbours, so
61
+ // per-char NFD length accumulates monotonically and origStart / origEnd
62
+ // are recoverable in O(N) total work.
63
+ //
64
+ // Invariants:
65
+ // • Returned string === content.substring(start, end) for some
66
+ // (start, end); never synthesised from normalised bytes.
67
+ // • At least one side drifts under normalisation; otherwise the exact
68
+ // tier already covered it (early bail).
69
+ // • Exactly one occurrence of nfdSearch in nfdContent — multi-match
70
+ // collapses to null so the caller still receives Error [code 8].
71
+ // • origStart lands on an original-content char boundary, not in the
72
+ // middle of an NFD decomposition.
73
+ export function nfcFoldMatch(content, searchStr) {
74
+ if (typeof content !== 'string' || typeof searchStr !== 'string') return null;
75
+ if (searchStr.length === 0 || content.length === 0) return null;
76
+ let nfdSearch;
77
+ let nfdContent;
78
+ try {
79
+ nfdSearch = searchStr.normalize('NFD');
80
+ nfdContent = content.normalize('NFD');
81
+ } catch {
82
+ return null;
83
+ }
84
+ if (nfdSearch === searchStr && nfdContent === content) return null;
85
+ if (nfdSearch.length === 0) return null;
86
+ const firstIdx = nfdContent.indexOf(nfdSearch);
87
+ if (firstIdx === -1) return null;
88
+ if (nfdContent.indexOf(nfdSearch, firstIdx + 1) !== -1) return null;
89
+ let origStart = 0;
90
+ let nfdCursor = 0;
91
+ while (origStart < content.length && nfdCursor < firstIdx) {
92
+ nfdCursor += content[origStart].normalize('NFD').length;
93
+ origStart++;
94
+ }
95
+ if (nfdCursor !== firstIdx) return null;
96
+ const targetLen = nfdSearch.length;
97
+ let endNfd = 0;
98
+ let origEnd = origStart;
99
+ while (origEnd < content.length) {
100
+ endNfd += content[origEnd].normalize('NFD').length;
101
+ origEnd++;
102
+ if (endNfd === targetLen) {
103
+ const slice = content.substring(origStart, origEnd);
104
+ return slice.normalize('NFD') === nfdSearch ? slice : null;
105
+ }
106
+ if (endNfd > targetLen) return null;
107
+ }
108
+ return null;
109
+ }
110
+
111
+ function _lineNumbersForIndices(content, indices, max = 3) {
112
+ const lines = [];
113
+ for (let k = 0; k < Math.min(max, indices.length); k++) {
114
+ const idx = indices[k];
115
+ let lineNo = 1;
116
+ for (let i = 0; i < idx; i++) {
117
+ if (content.charCodeAt(i) === 10) lineNo++;
118
+ }
119
+ lines.push(lineNo);
120
+ }
121
+ return lines;
122
+ }
123
+
124
+ function _collectOverlapAwareIndices(haystack, needle, limit = 64) {
125
+ const indices = [];
126
+ let idx = 0;
127
+ while ((idx = haystack.indexOf(needle, idx)) !== -1) {
128
+ indices.push(idx);
129
+ if (indices.length >= limit) break;
130
+ idx += 1;
131
+ }
132
+ return indices;
133
+ }
134
+
135
+
136
+ function _parseLineRecords(s) {
137
+ if (typeof s !== 'string' || s.length === 0) return [];
138
+ const parts = s.split(/(\r\n|\n|\r)/);
139
+ const records = [];
140
+ for (let i = 0; i < parts.length; i += 2) {
141
+ records.push({
142
+ body: parts[i] ?? '',
143
+ sep: i + 1 < parts.length ? parts[i + 1] : '',
144
+ });
145
+ }
146
+ return records;
147
+ }
148
+
149
+ function _lineRecordOffset(content, contentRecords, lineIdx) {
150
+ let off = 0;
151
+ for (let i = 0; i < lineIdx; i++) {
152
+ off += contentRecords[i].body.length + contentRecords[i].sep.length;
153
+ }
154
+ return off;
155
+ }
156
+
157
+ function _sliceFromLineWindow(content, contentRecords, startLine, searchRecords) {
158
+ const numLines = searchRecords.length;
159
+ const start = _lineRecordOffset(content, contentRecords, startLine);
160
+ let len = 0;
161
+ for (let j = 0; j < numLines; j++) {
162
+ const rec = contentRecords[startLine + j];
163
+ const sRec = searchRecords[j];
164
+ len += rec.body.length;
165
+ const isLast = j === numLines - 1;
166
+ if (!isLast) len += rec.sep.length;
167
+ else if (sRec.sep !== '') len += rec.sep.length;
168
+ }
169
+ return content.substring(start, start + len);
170
+ }
171
+
172
+ function _lineRecordsMatch(contentRecords, searchRecords, startLine, normalizeBody) {
173
+ const n = searchRecords.length;
174
+ if (startLine + n > contentRecords.length) return false;
175
+ for (let j = 0; j < n; j++) {
176
+ const cRec = contentRecords[startLine + j];
177
+ const sRec = searchRecords[j];
178
+ if (normalizeBody(cRec.body) !== normalizeBody(sRec.body)) return false;
179
+ const isLast = j === n - 1;
180
+ if (isLast && sRec.sep === '') continue;
181
+ if (cRec.sep !== sRec.sep) return false;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ function _normalizeLineBodyRstrip(body) {
187
+ return String(body).replace(/[ \t]+$/, '');
188
+ }
189
+
190
+ // CC parity: convertLeadingTabsToSpaces (leading tab run only, 2 spaces/tab).
191
+ function _normalizeLeadingIndentLine(body) {
192
+ const s = String(body);
193
+ if (!s.includes('\t')) return s;
194
+ const m = s.match(/^(\t+)(.*)$/);
195
+ if (!m) return s;
196
+ return `${' '.repeat(m[1].length)}${m[2]}`;
197
+ }
198
+
199
+ function _lineOrientedFoldMatch(content, searchStr, normalizeBody) {
200
+ const searchRecords = _parseLineRecords(searchStr);
201
+ const contentRecords = _parseLineRecords(content);
202
+ if (searchRecords.length === 0) return null;
203
+ let slice = null;
204
+ for (let start = 0; start + searchRecords.length <= contentRecords.length; start++) {
205
+ if (!_lineRecordsMatch(contentRecords, searchRecords, start, normalizeBody)) continue;
206
+ if (slice !== null) return null;
207
+ slice = _sliceFromLineWindow(content, contentRecords, start, searchRecords);
208
+ if (!content.includes(slice)) return null;
209
+ }
210
+ return slice;
211
+ }
212
+
213
+ function _diagnoseLineOrientedFold(content, searchStr, normalizeBody, stage) {
214
+ const searchRecords = _parseLineRecords(searchStr);
215
+ const contentRecords = _parseLineRecords(content);
216
+ const starts = [];
217
+ for (let start = 0; start + searchRecords.length <= contentRecords.length; start++) {
218
+ if (_lineRecordsMatch(contentRecords, searchRecords, start, normalizeBody)) starts.push(start);
219
+ }
220
+ if (starts.length <= 1) return null;
221
+ const lines = starts.map((st) => _lineNumbersForIndices(content, [_lineRecordOffset(content, contentRecords, st)], 1)[0] || 1);
222
+ return { stage, count: starts.length, lines };
223
+ }
224
+
225
+ function _eolFoldVariants(searchStr) {
226
+ const variants = [];
227
+ const endsWithCrlf = searchStr.endsWith('\r\n');
228
+ const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
229
+ if (!endsWithCrlf && !endsWithLf) {
230
+ variants.push(`${searchStr}\n`, `${searchStr}\r\n`);
231
+ } else if (endsWithCrlf) {
232
+ const bare = searchStr.slice(0, -2);
233
+ if (bare.length > 0) variants.push(bare);
234
+ } else if (endsWithLf) {
235
+ const bare = searchStr.slice(0, -1);
236
+ if (bare.length > 0) variants.push(bare);
237
+ }
238
+ return variants;
239
+ }
240
+
241
+ function _isTrailingEolBoundary(content, endIdx) {
242
+ if (endIdx >= content.length) return true;
243
+ if (content.charCodeAt(endIdx) === 10) return true;
244
+ if (content.charCodeAt(endIdx) === 13 && content.charCodeAt(endIdx + 1) === 10) return true;
245
+ return false;
246
+ }
247
+
248
+ function _sliceAtTrailingEolBoundary(content, start, bare, searchStr) {
249
+ const bareEnd = start + bare.length;
250
+ if (!_isTrailingEolBoundary(content, bareEnd)) return null;
251
+ if (bareEnd === content.length) return content.substring(start, bareEnd);
252
+ if (content.charCodeAt(bareEnd) === 13 && content.charCodeAt(bareEnd + 1) === 10) {
253
+ return content.substring(start, bareEnd + 2);
254
+ }
255
+ if (content.charCodeAt(bareEnd) === 10) return content.substring(start, bareEnd + 1);
256
+ return content.substring(start, bareEnd);
257
+ }
258
+
259
+ function _collectEolBoundaryBareIndices(content, bare) {
260
+ const indices = [];
261
+ let idx = 0;
262
+ while ((idx = content.indexOf(bare, idx)) !== -1) {
263
+ if (_isTrailingEolBoundary(content, idx + bare.length)) indices.push(idx);
264
+ idx += 1;
265
+ }
266
+ return indices;
267
+ }
268
+
269
+ function crlfFoldMatch(content, searchStr) {
270
+ const match = findCrlfNormalisedMatches(content, searchStr);
271
+ if (!match || match.ranges.length !== 1) return null;
272
+ const { start, end } = match.ranges[0];
273
+ return content.substring(start, end);
274
+ }
275
+
276
+ function _diagnoseCrlfFoldAmbiguity(content, searchStr) {
277
+ const match = findCrlfNormalisedMatches(content, searchStr);
278
+ if (!match || match.ranges.length <= 1) return null;
279
+ const indices = match.ranges.map((r) => r.start);
280
+ return {
281
+ stage: 'crlf-fold',
282
+ count: match.ranges.length,
283
+ lines: _lineNumbersForIndices(content, indices),
284
+ };
285
+ }
286
+
287
+ function eolFoldMatch(content, searchStr) {
288
+ if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) return null;
289
+ if (content.includes(searchStr)) return null;
290
+ const variants = _eolFoldVariants(searchStr);
291
+ const endsWithCrlf = searchStr.endsWith('\r\n');
292
+ const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
293
+ let slice = null;
294
+ let total = 0;
295
+ for (const needle of variants) {
296
+ if (!needle) continue;
297
+ const isBare = (endsWithCrlf || endsWithLf) && needle.length < searchStr.length;
298
+ const indices = isBare
299
+ ? _collectEolBoundaryBareIndices(content, needle)
300
+ : _collectOverlapAwareIndices(content, needle);
301
+ if (indices.length === 0) continue;
302
+ total += indices.length;
303
+ if (indices.length === 1 && slice === null) {
304
+ slice = isBare
305
+ ? _sliceAtTrailingEolBoundary(content, indices[0], needle, searchStr)
306
+ : content.substring(indices[0], indices[0] + needle.length);
307
+ } else if (indices.length > 0) {
308
+ if (indices.length > 1 || slice !== null) slice = null;
309
+ }
310
+ }
311
+ if (total === 1 && slice !== null) return slice;
312
+ return null;
313
+ }
314
+
315
+ function _diagnoseEolFoldAmbiguity(content, searchStr) {
316
+ const variants = _eolFoldVariants(searchStr);
317
+ const endsWithCrlf = searchStr.endsWith('\r\n');
318
+ const endsWithLf = !endsWithCrlf && searchStr.endsWith('\n');
319
+ const indices = [];
320
+ for (const needle of variants) {
321
+ if (!needle) continue;
322
+ const isBare = (endsWithCrlf || endsWithLf) && needle.length < searchStr.length;
323
+ if (isBare) indices.push(..._collectEolBoundaryBareIndices(content, needle));
324
+ else indices.push(..._collectOverlapAwareIndices(content, needle));
325
+ }
326
+ if (indices.length <= 1) return null;
327
+ return { stage: 'eol-fold', count: indices.length, lines: _lineNumbersForIndices(content, indices) };
328
+ }
329
+
330
+ // Smoke / audit: eol-fold ambiguity in isolation (ignores exact-tier literal hits).
331
+ export function diagnoseEolFoldAmbiguity(content, searchStr) {
332
+ return _diagnoseEolFoldAmbiguity(content, searchStr);
333
+ }
334
+
335
+ // When invariant-safe or last-resort fold tiers would match more than once, surface code 9
336
+ // (ambiguous) instead of falling through to code 8 (not found).
337
+ export function diagnoseFoldTierAmbiguity(content, searchStr) {
338
+ if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) {
339
+ return null;
340
+ }
341
+ if (content.includes(searchStr)) return null;
342
+
343
+ const nContent = normalizeForMatch(content);
344
+ const nSearch = normalizeForMatch(searchStr);
345
+ if (nContent !== content || nSearch !== searchStr) {
346
+ const indices = _collectOverlapAwareIndices(nContent, nSearch);
347
+ if (indices.length > 1) {
348
+ return {
349
+ stage: 'fold',
350
+ count: indices.length,
351
+ lines: _lineNumbersForIndices(content, indices),
352
+ };
353
+ }
354
+ }
355
+
356
+ let nfdSearch;
357
+ let nfdContent;
358
+ try {
359
+ nfdSearch = searchStr.normalize('NFD');
360
+ nfdContent = content.normalize('NFD');
361
+ } catch {
362
+ return null;
363
+ }
364
+ const nfdDrift = !(nfdSearch === searchStr && nfdContent === content);
365
+ if (nfdDrift && nfdSearch.length > 0) {
366
+ const nfdIndices = _collectOverlapAwareIndices(nfdContent, nfdSearch);
367
+ if (nfdIndices.length > 1) {
368
+ const lines = [];
369
+ for (let k = 0; k < Math.min(3, nfdIndices.length); k++) {
370
+ const firstIdx = nfdIndices[k];
371
+ let origStart = 0;
372
+ let nfdCursor = 0;
373
+ while (origStart < content.length && nfdCursor < firstIdx) {
374
+ nfdCursor += content[origStart].normalize('NFD').length;
375
+ origStart++;
376
+ }
377
+ lines.push(_lineNumbersForIndices(content, [origStart], 1)[0] || 1);
378
+ }
379
+ return { stage: 'nfc-fold', count: nfdIndices.length, lines };
380
+ }
381
+ }
382
+
383
+ const crlfAmb = _diagnoseCrlfFoldAmbiguity(content, searchStr);
384
+ if (crlfAmb) return crlfAmb;
385
+ const rstripAmb = _diagnoseLineOrientedFold(content, searchStr, _normalizeLineBodyRstrip, 'rstrip-fold');
386
+ if (rstripAmb) return rstripAmb;
387
+ const indentAmb = _diagnoseLineOrientedFold(content, searchStr, _normalizeLeadingIndentLine, 'indent-fold');
388
+ if (indentAmb) return indentAmb;
389
+ return _diagnoseEolFoldAmbiguity(content, searchStr);
390
+ }
391
+
392
+ // Tiered matcher: exact → curly-quote fold → NFC-fold → crlf-fold →
393
+ // rstrip-fold → indent-fold → eol-fold (last resort). `info.stage` is set
394
+ // to the tier that landed. Returns null if every tier fails — the caller
395
+ // may still attempt engine CRLF range replace or surfaces Error [code 8].
396
+ export function findActualString(content, searchStr, info) {
397
+ if (typeof content !== 'string' || typeof searchStr !== 'string' || searchStr.length === 0) return null;
398
+ if (content.includes(searchStr)) {
399
+ if (info) info.stage = 'exact';
400
+ return searchStr;
401
+ }
402
+ const nContent = normalizeForMatch(content);
403
+ const nSearch = normalizeForMatch(searchStr);
404
+ if (nContent !== content || nSearch !== searchStr) {
405
+ const idx = nContent.indexOf(nSearch);
406
+ if (idx !== -1) {
407
+ if (nContent.indexOf(nSearch, idx + 1) === -1) {
408
+ if (info) info.stage = 'fold';
409
+ return content.substring(idx, idx + searchStr.length);
410
+ }
411
+ }
412
+ }
413
+ const nfcSlice = nfcFoldMatch(content, searchStr);
414
+ if (nfcSlice !== null) {
415
+ if (info) info.stage = 'nfc-fold';
416
+ return nfcSlice;
417
+ }
418
+ const crlfSlice = crlfFoldMatch(content, searchStr);
419
+ if (crlfSlice !== null) {
420
+ if (info) info.stage = 'crlf-fold';
421
+ return crlfSlice;
422
+ }
423
+ const rstripSlice = _lineOrientedFoldMatch(content, searchStr, _normalizeLineBodyRstrip);
424
+ if (rstripSlice !== null) {
425
+ if (info) info.stage = 'rstrip-fold';
426
+ return rstripSlice;
427
+ }
428
+ const indentSlice = _lineOrientedFoldMatch(content, searchStr, _normalizeLeadingIndentLine);
429
+ if (indentSlice !== null) {
430
+ if (info) info.stage = 'indent-fold';
431
+ return indentSlice;
432
+ }
433
+ const eolSlice = eolFoldMatch(content, searchStr);
434
+ if (eolSlice !== null) {
435
+ if (info) info.stage = 'eol-fold';
436
+ return eolSlice;
437
+ }
438
+ return null;
439
+ }
440
+
441
+ // Strip trailing space/tab from each line of `s` while preserving every
442
+ // line terminator exactly as encountered (LF, CRLF, or lone CR). Used for
443
+ // insert-side new_string normalization and as the rstrip-fold view (match
444
+ // location only via findActualString's last-resort tier + ambiguity gate).
445
+ // Models routinely append stray spaces at line ends; in source code that
446
+ // has no semantic meaning, so silent diffs from those bytes are pure
447
+ // noise. Caller is responsible for skipping markdown (where `" \n"` is
448
+ // the hard-line-break syntax).
449
+ export function stripTrailingWhitespacePerLine(s) {
450
+ if (typeof s !== 'string' || s.length === 0) return s;
451
+ const parts = s.split(/(\r\n|\n|\r)/);
452
+ let changed = false;
453
+ for (let i = 0; i < parts.length; i += 2) {
454
+ const before = parts[i];
455
+ if (before === undefined) continue;
456
+ const after = before.replace(/[ \t]+$/, '');
457
+ if (after !== before) {
458
+ parts[i] = after;
459
+ changed = true;
460
+ }
461
+ }
462
+ return changed ? parts.join('') : s;
463
+ }
464
+
465
+ // Trailing-whitespace hygiene for Edit new_string, FINAL-LINE AWARE. The
466
+ // per-line strip exists to drop model-emitted trailing-space noise, but when
467
+ // old_string itself ends in space/tab the edit span deliberately ends
468
+ // MID-LINE inside meaningful whitespace (old `const m = ` / new
469
+ // `const mX = `) — stripping new_string's final line there silently eats a
470
+ // load-bearing space and corrupts the line. Non-final lines always end at a
471
+ // terminator, so they are unconditionally safe to strip.
472
+ export function stripTrailingWhitespaceForEdit(newString, oldString) {
473
+ if (typeof newString !== 'string' || newString.length === 0) return newString;
474
+ if (!/[ \t]$/.test(String(oldString ?? ''))) {
475
+ return stripTrailingWhitespacePerLine(newString);
476
+ }
477
+ const i = newString.lastIndexOf('\n');
478
+ if (i === -1) return newString;
479
+ return stripTrailingWhitespacePerLine(newString.slice(0, i + 1)) + newString.slice(i + 1);
480
+ }
481
+
482
+ // ─────────────────────────────────────────────────────────────────────────
483
+ // Typography preservation — Claude Code parity (FileEditTool/utils.ts:96-199).
484
+ //
485
+ // CORE-PRINCIPLE EXCEPTION: every tier in this module is a deterministic,
486
+ // invariant-based recovery; this single helper is a heuristic — explicitly
487
+ // opted in because the alternative (always emitting ASCII `'` / `"` into a
488
+ // curly-quoted file) silently drifts the typography of every edited prose
489
+ // file. Note this only rewrites `newString` bytes the model is about to
490
+ // insert; it never affects WHERE the match lands. The heuristic is
491
+ // well-bounded:
492
+ //
493
+ // • Only ASCII `'` / `"` characters in `newString` are touched. All
494
+ // other bytes — letters, numbers, punctuation, code identifiers,
495
+ // existing curly quotes — pass through unmodified.
496
+ // • Triggered only when `matchedSlice` (the file's actual bytes at the
497
+ // match position) contains at least one curly quote AND differs from
498
+ // `oldString` (i.e. the model emitted straight, the file had curly).
499
+ // • Contraction guard: an apostrophe between two Unicode letters is
500
+ // classified as a contraction (`it's`, `don't`) and always becomes
501
+ // RIGHT_SINGLE (`'`), regardless of preceding-char context.
502
+ // • LEFT / RIGHT inference: an opening quote is one preceded by
503
+ // whitespace, an opening bracket, an em / en dash, or string start.
504
+ // Otherwise closing.
505
+ //
506
+ // Known limitations (accepted):
507
+ // • Mid-sentence quote in code string literals — irrelevant because
508
+ // code files almost never contain curly quotes (skip-by-design via
509
+ // the `hasDoubleQuotes || hasSingleQuotes` gate).
510
+ // • CJK / Korean prose where quote-adjacent characters are letters —
511
+ // the contraction guard may fire and pick RIGHT. Net effect: a
512
+ // consistent right-quote rather than a drift back to ASCII, which
513
+ // is still a typography preservation, just not perfectly directional.
514
+ // ─────────────────────────────────────────────────────────────────────────
515
+
516
+ export const LEFT_SINGLE_CURLY_QUOTE = '‘';
517
+ export const RIGHT_SINGLE_CURLY_QUOTE = '’';
518
+ export const LEFT_DOUBLE_CURLY_QUOTE = '“';
519
+ export const RIGHT_DOUBLE_CURLY_QUOTE = '”';
520
+
521
+ function _isOpeningContext(chars, index) {
522
+ if (index === 0) return true;
523
+ const prev = chars[index - 1];
524
+ return (
525
+ prev === ' '
526
+ || prev === '\t'
527
+ || prev === '\n'
528
+ || prev === '\r'
529
+ || prev === '('
530
+ || prev === '['
531
+ || prev === '{'
532
+ || prev === '—' // em dash
533
+ || prev === '–' // en dash
534
+ );
535
+ }
536
+
537
+ function _applyCurlyDoubleQuotes(str) {
538
+ const chars = Array.from(str);
539
+ const result = [];
540
+ for (let i = 0; i < chars.length; i++) {
541
+ if (chars[i] === '"') {
542
+ result.push(_isOpeningContext(chars, i) ? LEFT_DOUBLE_CURLY_QUOTE : RIGHT_DOUBLE_CURLY_QUOTE);
543
+ } else {
544
+ result.push(chars[i]);
545
+ }
546
+ }
547
+ return result.join('');
548
+ }
549
+
550
+ function _applyCurlySingleQuotes(str) {
551
+ const chars = Array.from(str);
552
+ const result = [];
553
+ for (let i = 0; i < chars.length; i++) {
554
+ if (chars[i] === "'") {
555
+ const prev = i > 0 ? chars[i - 1] : undefined;
556
+ const next = i < chars.length - 1 ? chars[i + 1] : undefined;
557
+ const prevIsLetter = prev !== undefined && /\p{L}/u.test(prev);
558
+ const nextIsLetter = next !== undefined && /\p{L}/u.test(next);
559
+ if (prevIsLetter && nextIsLetter) {
560
+ // Contraction (e.g. "it's", "don't") — always right curly.
561
+ result.push(RIGHT_SINGLE_CURLY_QUOTE);
562
+ } else {
563
+ result.push(_isOpeningContext(chars, i) ? LEFT_SINGLE_CURLY_QUOTE : RIGHT_SINGLE_CURLY_QUOTE);
564
+ }
565
+ } else {
566
+ result.push(chars[i]);
567
+ }
568
+ }
569
+ return result.join('');
570
+ }
571
+
572
+ export function preserveQuoteTypography(oldString, matchedSlice, newString) {
573
+ if (typeof matchedSlice !== 'string' || typeof newString !== 'string') return newString;
574
+ if (oldString === matchedSlice) return newString;
575
+ const hasDoubleQuotes =
576
+ matchedSlice.includes(LEFT_DOUBLE_CURLY_QUOTE) ||
577
+ matchedSlice.includes(RIGHT_DOUBLE_CURLY_QUOTE);
578
+ const hasSingleQuotes =
579
+ matchedSlice.includes(LEFT_SINGLE_CURLY_QUOTE) ||
580
+ matchedSlice.includes(RIGHT_SINGLE_CURLY_QUOTE);
581
+ if (!hasDoubleQuotes && !hasSingleQuotes) return newString;
582
+ let result = newString;
583
+ if (hasDoubleQuotes) result = _applyCurlyDoubleQuotes(result);
584
+ if (hasSingleQuotes) result = _applyCurlySingleQuotes(result);
585
+ return result;
586
+ }
587
+
588
+ // Single-stage inline note → " (via fold)" suffix for error messages.
589
+ // Empty when the stage is missing or exact (the steady-state path).
590
+ export function formatStageInline(stage) {
591
+ return (stage && stage !== 'exact') ? ` (via ${stage})` : '';
592
+ }
593
+
594
+ // Stage stats → " [fold:2, crlf-fold:0]" suffix. Empty / null counts
595
+ // render as empty string so exact-only edits stay terse. Stable key order
596
+ // (Object.entries insertion order) keeps two edits with the same matches
597
+ // rendering identically.
598
+ export function formatStageNote(stageCounts) {
599
+ if (!stageCounts) return '';
600
+ const entries = Object.entries(stageCounts).filter(([, v]) => v > 0);
601
+ if (entries.length === 0) return '';
602
+ return ` [${entries.map(([k, v]) => `${k}:${v}`).join(', ')}]`;
603
+ }