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,240 @@
1
+ import { stripTrailingWhitespaceForEdit } from '../edit-normalize.mjs';
2
+ import {
3
+ bufferWithTrailingLf,
4
+ concatByteReplacements,
5
+ hashBytesWithReplacements,
6
+ } from './edit-byte-utils.mjs';
7
+ import {
8
+ countLfInString,
9
+ maybeAutoStripLineNumberPrefixes,
10
+ } from './edit-context-utils.mjs';
11
+ import { replacementForOriginalSlice } from './edit-match-utils.mjs';
12
+
13
+ export function tryBuildExactEditBuffer(rawBuf, oldStr, newStr, replaceAll, snapshot, filePath) {
14
+ if (!Buffer.isBuffer(rawBuf) || typeof oldStr !== 'string' || oldStr.length === 0 || typeof newStr !== 'string') return null;
15
+ const oldBytes = Buffer.from(oldStr, 'utf-8');
16
+ if (oldBytes.length === 0) return null;
17
+ let eOldStr = oldStr;
18
+ let eOldBytes = oldBytes;
19
+ const _pureDeletion = newStr === '' && !oldStr.endsWith('\n') && !oldStr.endsWith('\r');
20
+ // Single-occurrence pure deletion: probe globally so the unique
21
+ // match also covers its trailing line terminator (CRLF first to
22
+ // avoid leaving a stray \r on CRLF files, then LF, then lone-CR).
23
+ // Replace-all pure deletion CANNOT use a single global rewrite of
24
+ // eOldBytes — when occurrences are mixed (some followed by \n, some
25
+ // bare / at EOF), an eOldBytes that includes the terminator would
26
+ // silently skip every bare occurrence. Per-occurrence absorption
27
+ // below extends each span over its own trailing \r\n / \n / \r so
28
+ // every match is removed regardless of what follows it.
29
+ if (_pureDeletion && !replaceAll) {
30
+ // Judge ambiguity on the BARE oldBytes BEFORE newline absorption.
31
+ // Absorbing a trailing terminator first can narrow a >1-occurrence
32
+ // bare match down to a unique extended span (e.g. 'X' present as
33
+ // 'X\r\n...X'), silently single-deleting instead of surfacing the
34
+ // ambiguous-match error. If the bare oldBytes occurs more than once
35
+ // return null — the same signal the spans.length>1 path emits (→ the
36
+ // caller's code-9 ambiguous-match error). Only absorb when the bare
37
+ // match is unique.
38
+ const _firstBare = rawBuf.indexOf(oldBytes);
39
+ if (_firstBare !== -1 && rawBuf.indexOf(oldBytes, _firstBare + 1) !== -1) {
40
+ return null;
41
+ }
42
+ const oldWithCrlf = Buffer.from(`${oldStr}\r\n`, 'utf-8');
43
+ const oldWithLf = bufferWithTrailingLf(oldBytes);
44
+ const oldWithCr = Buffer.from(`${oldStr}\r`, 'utf-8');
45
+ if (rawBuf.indexOf(oldWithCrlf) !== -1) {
46
+ eOldStr = `${oldStr}\r\n`;
47
+ eOldBytes = oldWithCrlf;
48
+ } else if (rawBuf.indexOf(oldWithLf) !== -1) {
49
+ eOldStr = `${oldStr}\n`;
50
+ eOldBytes = oldWithLf;
51
+ } else if (rawBuf.indexOf(oldWithCr) !== -1) {
52
+ eOldStr = `${oldStr}\r`;
53
+ eOldBytes = oldWithCr;
54
+ }
55
+ }
56
+ const _perOccurrenceAbsorb = _pureDeletion && replaceAll;
57
+ if (!replaceAll) {
58
+ const _fb = rawBuf.indexOf(eOldBytes);
59
+ if (_fb !== -1 && rawBuf.indexOf(eOldBytes, _fb + 1) !== -1) return null;
60
+ }
61
+ const spans = [];
62
+ let idx = 0;
63
+ while ((idx = rawBuf.indexOf(eOldBytes, idx)) !== -1) {
64
+ let _end = idx + eOldBytes.length;
65
+ if (_perOccurrenceAbsorb) {
66
+ // CRLF (\r\n) first so we don't strand a stray \r; then LF;
67
+ // then lone-CR. Bare / EOF occurrences leave _end as-is.
68
+ if (rawBuf[_end] === 0x0d && rawBuf[_end + 1] === 0x0a) _end += 2;
69
+ else if (rawBuf[_end] === 0x0a) _end += 1;
70
+ else if (rawBuf[_end] === 0x0d) _end += 1;
71
+ }
72
+ spans.push({ start: idx, end: _end });
73
+ if (!replaceAll && spans.length > 1) return null;
74
+ idx += eOldBytes.length;
75
+ }
76
+ if (spans.length === 0) return null;
77
+ const fileUtf8 = rawBuf.toString('utf-8');
78
+ const replacements = spans.map((span) => ({
79
+ ...span,
80
+ newBytes: Buffer.from(
81
+ replacementForOriginalSlice(newStr, rawBuf.subarray(span.start, span.end).toString('utf-8'), fileUtf8),
82
+ 'utf-8',
83
+ ),
84
+ }));
85
+ const sameSize = replacements.every((span) => span.end - span.start === span.newBytes.length);
86
+ if (spans.length === 1) {
87
+ const first = spans[0].start;
88
+ if (sameSize) {
89
+ return {
90
+ replacements,
91
+ sameSize: true,
92
+ contentHash: hashBytesWithReplacements(rawBuf, replacements),
93
+ };
94
+ }
95
+ return {
96
+ replacements,
97
+ sameSize: false,
98
+ updated: Buffer.concat([
99
+ rawBuf.subarray(0, first),
100
+ replacements[0].newBytes,
101
+ rawBuf.subarray(spans[0].end),
102
+ ], rawBuf.length - (spans[0].end - first) + replacements[0].newBytes.length),
103
+ };
104
+ }
105
+ if (sameSize) {
106
+ return {
107
+ replacements,
108
+ sameSize: true,
109
+ contentHash: hashBytesWithReplacements(rawBuf, replacements),
110
+ };
111
+ }
112
+ return {
113
+ replacements,
114
+ sameSize: false,
115
+ updated: concatByteReplacements(rawBuf, replacements),
116
+ };
117
+ }
118
+
119
+ export function tryBuildMultiExactEditBuffer(rawBuf, edits, args, snapshot, filePath) {
120
+ if (!Buffer.isBuffer(rawBuf) || !Array.isArray(edits) || edits.length === 0) return null;
121
+ // Fast path treats every edit as locating its span in the ORIGINAL buffer
122
+ // and applies all replacements at once. Sequential-apply semantics differ
123
+ // whenever edit A's new_string can synthesise (or destroy) bytes that
124
+ // edit B's old_string would match. Disable the fast path unless edits
125
+ // are provably independent — no pair where one's new_string contains
126
+ // another's old_string. The slow path runs them sequentially.
127
+ if (edits.length > 1) {
128
+ for (let a = 0; a < edits.length; a++) {
129
+ const ea = edits[a];
130
+ if (!ea || typeof ea.old_string !== 'string' || typeof ea.new_string !== 'string') return null;
131
+ for (let b = 0; b < edits.length; b++) {
132
+ if (a === b) continue;
133
+ const eb = edits[b];
134
+ if (!eb || typeof eb.old_string !== 'string') return null;
135
+ if (eb.old_string.length === 0) continue;
136
+ if (ea.new_string.indexOf(eb.old_string) !== -1) return null;
137
+ }
138
+ }
139
+ }
140
+ const isMarkdown = /\.(?:md|mdx)$/i.test(filePath);
141
+ const replacements = [];
142
+ const normalizedEdits = [];
143
+ for (let i = 0; i < edits.length; i++) {
144
+ const entry = edits[i];
145
+ if (!entry || typeof entry.old_string !== 'string' || typeof entry.new_string !== 'string') return null;
146
+ let oldString = entry.old_string;
147
+ let newString = entry.new_string;
148
+ {
149
+ const _nulIdx = newString.indexOf('\u0000');
150
+ if (_nulIdx !== -1) {
151
+ return { error: `Error [code 11]: edit ${i} — new_string contains NUL byte (U+0000) at offset ${_nulIdx} — source text must not contain NUL: ${filePath}` };
152
+ }
153
+ }
154
+ const replaceAll = entry.replace_all === true;
155
+ // Size gate moved out of the pre-loop guard: the exact byte
156
+ // path proves uniqueness below (spans.length === 1 for
157
+ // !replaceAll), so an exact-unique multi edit applies at any
158
+ // size. The fold-fallback path in edit-engine still enforces
159
+ // the >=30-line code-10 wording for genuine fold-misses.
160
+ if (/^\s*\d+[\t│→]/.test(oldString)) {
161
+ const stripped = maybeAutoStripLineNumberPrefixes(oldString);
162
+ if (stripped === null) return null;
163
+ oldString = stripped;
164
+ }
165
+ // Final-line-aware hygiene — same rule as the edit-engine slow path
166
+ // (stripTrailingWhitespaceForEdit), so fast/slow paths cannot diverge
167
+ // on identical inputs.
168
+ if (!isMarkdown) newString = stripTrailingWhitespaceForEdit(newString, oldString);
169
+ if (oldString.length === 0) {
170
+ return { error: `Error: edit ${i} — old_string must be non-empty` };
171
+ }
172
+ if (oldString === newString) {
173
+ return { error: `Error: edit ${i} — new_string must differ from old_string` };
174
+ }
175
+ if (newString === '' || countLfInString(oldString) !== countLfInString(newString)) return null;
176
+ const oldBytes = Buffer.from(oldString, 'utf-8');
177
+ const fileUtf8 = rawBuf.toString('utf-8');
178
+ if (oldBytes.length === 0) return null;
179
+ if (!replaceAll) {
180
+ const _fb = rawBuf.indexOf(oldBytes);
181
+ if (_fb !== -1 && rawBuf.indexOf(oldBytes, _fb + 1) !== -1) return null;
182
+ }
183
+ normalizedEdits.push({ oldString, newString, replaceAll });
184
+ const spans = [];
185
+ let idx = 0;
186
+ while ((idx = rawBuf.indexOf(oldBytes, idx)) !== -1) {
187
+ const spanEnd = idx + oldBytes.length;
188
+ const sliceText = rawBuf.subarray(idx, spanEnd).toString('utf-8');
189
+ const newBytes = Buffer.from(
190
+ replacementForOriginalSlice(newString, sliceText, fileUtf8),
191
+ 'utf-8',
192
+ );
193
+ spans.push({ start: idx, end: spanEnd, editIndex: i, newBytes });
194
+ if (!replaceAll && spans.length > 1) return null;
195
+ idx += oldBytes.length;
196
+ }
197
+ if (spans.length === 0) return null;
198
+ replacements.push(...spans);
199
+ }
200
+ replacements.sort((a, b) => a.start - b.start || a.end - b.end);
201
+ for (let i = 1; i < replacements.length; i++) {
202
+ if (replacements[i].start < replacements[i - 1].end) return null;
203
+ }
204
+ // Independence guard (above) only checks new_string-contains-old_string; it
205
+ // misses old_strings SYNTHESISED across edit boundaries (e.g. "A"->"y" turns
206
+ // "Az yz" into "yz yz", making a later "yz"->Q match twice — which sequential
207
+ // apply would reject as ambiguous). Verify the fast (apply-in-original)
208
+ // result equals an in-order SEQUENTIAL replay; on any divergence or
209
+ // sequential-reject, return null so the caller runs the slow sequential
210
+ // path. Fail-safe: a false mismatch only costs a fallback, never corruption.
211
+ {
212
+ const _fastBytes = concatByteReplacements(rawBuf, replacements);
213
+ let seq = rawBuf.toString('utf-8');
214
+ for (const { oldString: _o, newString: _n, replaceAll: _ra } of normalizedEdits) {
215
+ const repl = replacementForOriginalSlice(_n, _o, seq);
216
+ if (_ra) {
217
+ if (!seq.includes(_o)) return null;
218
+ seq = seq.split(_o).join(repl);
219
+ } else {
220
+ const first = seq.indexOf(_o);
221
+ if (first === -1 || seq.indexOf(_o, first + _o.length) !== -1) return null;
222
+ seq = seq.slice(0, first) + repl + seq.slice(first + _o.length);
223
+ }
224
+ }
225
+ if (!Buffer.from(seq, 'utf-8').equals(_fastBytes)) return null;
226
+ }
227
+ const sameSize = replacements.every((span) => span.end - span.start === span.newBytes.length);
228
+ if (sameSize) {
229
+ return {
230
+ replacements,
231
+ sameSize: true,
232
+ contentHash: hashBytesWithReplacements(rawBuf, replacements),
233
+ };
234
+ }
235
+ return {
236
+ replacements,
237
+ sameSize: false,
238
+ updated: concatByteReplacements(rawBuf, replacements),
239
+ };
240
+ }
@@ -0,0 +1,113 @@
1
+ import { createHash } from 'crypto';
2
+ import { statSync } from 'fs';
3
+ import { hashText } from './hash-utils.mjs';
4
+ import { statMatchesSnapshot } from './snapshot-helpers.mjs';
5
+
6
+ export function lineRangesForByteSpans(rawBuf, spans) {
7
+ if (!Buffer.isBuffer(rawBuf) || !Array.isArray(spans) || spans.length === 0) return [];
8
+ const sorted = spans
9
+ .filter((span) => span && Number.isFinite(span.start) && Number.isFinite(span.end) && span.start >= 0 && span.end >= span.start)
10
+ .sort((a, b) => a.start - b.start);
11
+ const out = [];
12
+ let lineNo = 1;
13
+ let scanned = 0;
14
+ for (const span of sorted) {
15
+ const start = Math.min(rawBuf.length, span.start);
16
+ const end = Math.min(rawBuf.length, span.end);
17
+ for (let i = scanned; i < start; i++) {
18
+ if (rawBuf[i] === 10) lineNo++;
19
+ }
20
+ const startLine = lineNo;
21
+ let lineCount = 1;
22
+ for (let i = start; i < end; i++) {
23
+ if (rawBuf[i] === 10) lineCount++;
24
+ }
25
+ for (let i = start; i < end; i++) {
26
+ if (rawBuf[i] === 10) lineNo++;
27
+ }
28
+ scanned = end;
29
+ out.push({ startLine, endLine: startLine + lineCount - 1 });
30
+ }
31
+ return out;
32
+ }
33
+
34
+ export function bufferWithTrailingLf(buf) {
35
+ const out = Buffer.allocUnsafe(buf.length + 1);
36
+ buf.copy(out, 0);
37
+ out[buf.length] = 10;
38
+ return out;
39
+ }
40
+
41
+ export function concatByteReplacements(rawBuf, replacements) {
42
+ const parts = [];
43
+ let cursor = 0;
44
+ let totalLength = 0;
45
+ for (const span of replacements) {
46
+ if (span.start < cursor) return null;
47
+ const before = rawBuf.subarray(cursor, span.start);
48
+ parts.push(before, span.newBytes);
49
+ totalLength += before.length + span.newBytes.length;
50
+ cursor = span.end;
51
+ }
52
+ const tail = rawBuf.subarray(cursor);
53
+ parts.push(tail);
54
+ totalLength += tail.length;
55
+ return Buffer.concat(parts, totalLength);
56
+ }
57
+
58
+ export function hashBytesWithReplacements(rawBuf, replacements) {
59
+ const hasher = createHash('sha256');
60
+ let cursor = 0;
61
+ const sorted = Array.isArray(replacements)
62
+ ? replacements.slice().sort((a, b) => a.start - b.start || a.end - b.end)
63
+ : [];
64
+ for (const span of sorted) {
65
+ if (!span || span.start < cursor || span.end < span.start || !Buffer.isBuffer(span.newBytes)) return hashText(rawBuf);
66
+ hasher.update(rawBuf.subarray(cursor, span.start));
67
+ hasher.update(span.newBytes);
68
+ cursor = span.end;
69
+ }
70
+ hasher.update(rawBuf.subarray(cursor));
71
+ return hasher.digest('hex');
72
+ }
73
+
74
+ export function materialiseByteReplacements(rawBuf, replacements) {
75
+ return concatByteReplacements(rawBuf, replacements);
76
+ }
77
+
78
+ export function partialByteWriteEnabled() {
79
+ return !/^(0|false|no|off|atomic)$/i.test(String(process.env.MIXDOG_EDIT_PARTIAL_WRITE || process.env.MIXDOG_PARTIAL_WRITE || ''));
80
+ }
81
+
82
+ export function captureStableBaseStatSnapshot(fullPath, statHint, rawBuf) {
83
+ if (!fullPath || !statHint || !Buffer.isBuffer(rawBuf)) return null;
84
+ try {
85
+ const postReadStat = statSync(fullPath);
86
+ if (statMatchesSnapshot(postReadStat, statHint) && postReadStat.size === rawBuf.length) {
87
+ return {
88
+ mtimeMs: postReadStat.mtimeMs,
89
+ ctimeMs: postReadStat.ctimeMs,
90
+ size: postReadStat.size,
91
+ ino: Number(postReadStat.ino),
92
+ };
93
+ }
94
+ } catch {}
95
+ return null;
96
+ }
97
+
98
+ /** TOCTOU guard for atomicWrite — same shape as write-tool captureTargetSnapshot. */
99
+ export function captureExpectedTargetSnapshot(fullPath, statHint = null) {
100
+ try {
101
+ const st = statHint || statSync(fullPath);
102
+ return {
103
+ exists: true,
104
+ size: st.size,
105
+ mtimeMs: Number(st.mtimeMs),
106
+ ctimeMs: Number(st.ctimeMs),
107
+ ino: Number(st.ino),
108
+ };
109
+ } catch (err) {
110
+ if (err && err.code === 'ENOENT') return { exists: false };
111
+ throw err;
112
+ }
113
+ }
@@ -0,0 +1,74 @@
1
+ import { markCodeGraphDirtyPaths } from '../code-graph.mjs';
2
+ import { atomicWrite } from './atomic-write.mjs';
3
+ import {
4
+ invalidateBuiltinResultCache,
5
+ seedRawContentCacheAfterWrite,
6
+ } from './cache-layers.mjs';
7
+ import { captureExpectedTargetSnapshot, materialiseByteReplacements } from './edit-byte-utils.mjs';
8
+ import { postEditSnapshotMeta } from './edit-context-utils.mjs';
9
+ import { tryWriteSameSizeByteReplacementsSync } from './edit-partial-write.mjs';
10
+ import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
11
+ import { validatePreparedEditBase } from './edit-base-guard.mjs';
12
+
13
+ function commitHooks(hooks = {}) {
14
+ return {
15
+ ioTraceStart: typeof hooks.ioTraceStart === 'function' ? hooks.ioTraceStart : () => 0,
16
+ ioTraceDone: typeof hooks.ioTraceDone === 'function' ? hooks.ioTraceDone : () => {},
17
+ };
18
+ }
19
+
20
+ export async function commitPreparedEditUnlocked(prepared, readStateScope, options = {}, hooks = {}) {
21
+ const traceHooks = commitHooks(hooks);
22
+ if (Array.isArray(prepared.sameSizeByteReplacements) && prepared.sameSizeByteReplacements.length > 0) {
23
+ const partial = tryWriteSameSizeByteReplacementsSync(prepared.fullPath, prepared.sameSizeByteReplacements, {
24
+ baseStatSnapshot: prepared.baseStatSnapshot,
25
+ baseMutationGeneration: prepared.baseMutationGeneration,
26
+ baseContentHash: prepared.baseContentHash,
27
+ contentHash: prepared.contentHash,
28
+ fsync: options?.fsync,
29
+ filePath: prepared.filePath,
30
+ }, {
31
+ ...traceHooks,
32
+ validatePreparedEditBase,
33
+ });
34
+ if (partial?.ok) {
35
+ invalidateBuiltinResultCache([prepared.fullPath]);
36
+ markCodeGraphDirtyPaths([prepared.fullPath]);
37
+ const afterBuf = materialiseByteReplacements(prepared.baseRawContent, prepared.sameSizeByteReplacements);
38
+ const snapMeta = postEditSnapshotMeta(prepared.snapshot, 'edit', afterBuf, {
39
+ contentBeforeEdit: prepared.baseRawContent,
40
+ shiftRanges: false,
41
+ });
42
+ recordReadSnapshot(prepared.fullPath, partial.stat || undefined, readStateScope, snapMeta);
43
+ return;
44
+ }
45
+ if (partial?.error) throw new Error(partial.error.replace(/^Error:\s*/, ''));
46
+ if (!Buffer.isBuffer(prepared.content) && Buffer.isBuffer(prepared.baseRawContent)) {
47
+ prepared.content = materialiseByteReplacements(prepared.baseRawContent, prepared.sameSizeByteReplacements);
48
+ }
49
+ }
50
+ if (!Buffer.isBuffer(prepared.content) && typeof prepared.content !== 'string') {
51
+ throw new Error('prepared edit missing materialised content');
52
+ }
53
+ const expectedTargetSnapshot = prepared.expectedTargetSnapshot
54
+ || captureExpectedTargetSnapshot(prepared.fullPath);
55
+ await atomicWrite(prepared.fullPath, prepared.content, {
56
+ sessionId: options?.sessionId,
57
+ mode: prepared.baseMode,
58
+ expectedTargetSnapshot,
59
+ });
60
+ invalidateBuiltinResultCache([prepared.fullPath]);
61
+ const writtenStat = seedRawContentCacheAfterWrite(prepared.fullPath, prepared.content);
62
+ markCodeGraphDirtyPaths([prepared.fullPath]);
63
+ recordReadSnapshot(prepared.fullPath, writtenStat || undefined, readStateScope, postEditSnapshotMeta(prepared.snapshot, 'edit', prepared.content, {
64
+ contentBeforeEdit: prepared.baseRawContent,
65
+ shiftRanges: false,
66
+ }));
67
+ }
68
+
69
+ export async function commitPreparedEditCheckedUnlocked(prepared, readStateScope, options = {}, hooks = {}) {
70
+ const prewriteErr = validatePreparedEditBase(prepared);
71
+ if (prewriteErr) return { ok: false, error: prewriteErr };
72
+ await commitPreparedEditUnlocked(prepared, readStateScope, options, hooks);
73
+ return { ok: true };
74
+ }
@@ -0,0 +1,242 @@
1
+ import { hashText } from './hash-utils.mjs';
2
+ import { renderReadLine } from './read-formatting.mjs';
3
+ import {
4
+ snapshotCoversFullFile,
5
+ snapshotRangesCoverAllLines,
6
+ } from './snapshot-helpers.mjs';
7
+
8
+ export function countLfInString(value) {
9
+ let n = 0;
10
+ const s = String(value ?? '');
11
+ for (let i = 0; i < s.length; i++) {
12
+ if (s.charCodeAt(i) === 10) n++;
13
+ }
14
+ return n;
15
+ }
16
+
17
+ function normaliseSnapshotRange(r) {
18
+ if (!r) return null;
19
+ const startLine = Math.max(1, Number(r.startLine) || 1);
20
+ const endLine = r.endLine === Infinity ? Infinity : Number(r.endLine);
21
+ if (!Number.isFinite(startLine)) return null;
22
+ if (endLine !== Infinity && (!Number.isFinite(endLine) || endLine < startLine)) return null;
23
+ return { startLine, endLine };
24
+ }
25
+
26
+ function isFullFileSentinelRange(r) {
27
+ return r && r.startLine <= 1 && r.endLine === Infinity;
28
+ }
29
+
30
+ export function lineRangeForSubstring(content, needle, { replaceAll = false } = {}) {
31
+ if (typeof content !== 'string' || typeof needle !== 'string' || needle.length === 0) return null;
32
+ let oldLineCount = 1;
33
+ for (let i = 0; i < needle.length; i++) {
34
+ if (needle.charCodeAt(i) === 10) oldLineCount++;
35
+ }
36
+ // A needle ending in "\n" terminates its last content line rather than
37
+ // starting a new one. Without this, endLine over-counts by 1 and the edit
38
+ // marks the FOLLOWING line as part of its (inclusive) range, mis-shifting
39
+ // snapshot/context ranges. Reviewer-verified: ranges are inclusive
40
+ // end-to-end with no downstream compensation. No effect for needles that
41
+ // do not end in "\n".
42
+ if (needle.endsWith('\n')) oldLineCount--;
43
+ let idx = 0;
44
+ let scanned = 0;
45
+ let lineNo = 1;
46
+ let minStart = Infinity;
47
+ let maxEnd = 0;
48
+ let found = false;
49
+ while ((idx = content.indexOf(needle, idx)) !== -1) {
50
+ for (let i = scanned; i < idx; i++) {
51
+ if (content.charCodeAt(i) === 10) lineNo++;
52
+ }
53
+ scanned = idx;
54
+ const startLine = lineNo;
55
+ const endLine = startLine + oldLineCount - 1;
56
+ minStart = Math.min(minStart, startLine);
57
+ maxEnd = Math.max(maxEnd, endLine);
58
+ found = true;
59
+ idx += needle.length;
60
+ if (!replaceAll) break;
61
+ }
62
+ if (!found) return null;
63
+ return { startLine: minStart, endLine: maxEnd };
64
+ }
65
+
66
+ export function shiftSnapshotRangesForEdit(snapshot, opts = {}) {
67
+ if (!snapshot || !Array.isArray(snapshot.ranges)) return snapshot;
68
+ const lineDelta = Number(opts.lineDelta);
69
+ const delta = Number.isFinite(lineDelta) ? lineDelta : 0;
70
+ const editStart = Number(opts.editStartLine);
71
+ const editEnd = Number(opts.editEndLine);
72
+ const hasSpan = Number.isFinite(editStart) && Number.isFinite(editEnd) && editStart >= 1 && editEnd >= editStart;
73
+
74
+ const out = [];
75
+ for (const raw of snapshot.ranges) {
76
+ const r = normaliseSnapshotRange(raw);
77
+ if (!r) continue;
78
+ if (isFullFileSentinelRange(r)) {
79
+ out.push({ startLine: 1, endLine: Infinity });
80
+ continue;
81
+ }
82
+ let { startLine, endLine } = r;
83
+ if (!hasSpan) {
84
+ if (delta !== 0) {
85
+ startLine = Math.max(1, startLine + delta);
86
+ if (endLine !== Infinity) endLine = endLine + delta;
87
+ }
88
+ } else if (endLine !== Infinity && endLine < editStart) {
89
+ // entirely before the edited span
90
+ } else if (startLine > editEnd) {
91
+ startLine = Math.max(1, startLine + delta);
92
+ if (endLine !== Infinity) endLine = endLine + delta;
93
+ } else {
94
+ // overlaps the edited span — keep start, extend/shrink end safely
95
+ if (endLine !== Infinity) {
96
+ endLine = Math.max(startLine, endLine + delta);
97
+ }
98
+ }
99
+ if (endLine !== Infinity && endLine < startLine) continue;
100
+ out.push({ startLine, endLine });
101
+ }
102
+ return { ...snapshot, ranges: out };
103
+ }
104
+
105
+ export function shiftSnapshotRangesByLineDelta(snapshot, lineDelta) {
106
+ return shiftSnapshotRangesForEdit(snapshot, { lineDelta });
107
+ }
108
+
109
+ export function isStrongExactEditTarget(oldStr, stage) {
110
+ const s = String(oldStr ?? '').trim();
111
+ const fuzzy = stage && stage !== 'exact';
112
+ const singleLineMin = fuzzy ? 16 : 32;
113
+ const multiLineMin = fuzzy ? 10 : 20;
114
+ if (s.length >= singleLineMin) return true;
115
+ if (s.includes('\n') && s.length >= multiLineMin) return true;
116
+ return false;
117
+ }
118
+
119
+ function snapshotMetaHasFullCoverage(meta, content) {
120
+ if (!meta || !Array.isArray(meta.ranges)) return false;
121
+ if (snapshotCoversFullFile(meta)) return true;
122
+ const lineCount = String(content ?? '').split(/\r?\n/).length;
123
+ return snapshotRangesCoverAllLines(meta, lineCount);
124
+ }
125
+
126
+ export function postEditSnapshotMeta(prevSnapshot, source, content, opts = {}) {
127
+ const text = Buffer.isBuffer(content) ? content.toString('utf-8') : String(content ?? '');
128
+ let lineDelta = Number(opts.lineDelta);
129
+ if (!Number.isFinite(lineDelta) && opts.contentBeforeEdit != null) {
130
+ const before = Buffer.isBuffer(opts.contentBeforeEdit)
131
+ ? opts.contentBeforeEdit.toString('utf-8')
132
+ : String(opts.contentBeforeEdit ?? '');
133
+ const after = text;
134
+ lineDelta = countLfInString(after) - countLfInString(before);
135
+ }
136
+ if (!Number.isFinite(lineDelta) && opts.oldStr != null && opts.newStr != null) {
137
+ const per = countLfInString(opts.newStr) - countLfInString(opts.oldStr);
138
+ const beforeText = Buffer.isBuffer(opts.contentBeforeEdit)
139
+ ? opts.contentBeforeEdit.toString('utf-8')
140
+ : String(opts.contentBeforeEdit ?? '');
141
+ const occ = opts.replaceAll === true
142
+ ? Math.max(1, countOccurrences(beforeText, opts.oldStr))
143
+ : 1;
144
+ lineDelta = per * occ;
145
+ }
146
+ if (!Number.isFinite(lineDelta)) lineDelta = 0;
147
+
148
+ let editStartLine = Number(opts.editStartLine);
149
+ let editEndLine = Number(opts.editEndLine);
150
+ if (!(Number.isFinite(editStartLine) && Number.isFinite(editEndLine)) && typeof opts.oldStr === 'string' && opts.contentBeforeEdit != null) {
151
+ const beforeText = Buffer.isBuffer(opts.contentBeforeEdit)
152
+ ? opts.contentBeforeEdit.toString('utf-8')
153
+ : String(opts.contentBeforeEdit ?? '');
154
+ const span = lineRangeForSubstring(beforeText, opts.oldStr, { replaceAll: opts.replaceAll === true });
155
+ if (span) {
156
+ editStartLine = span.startLine;
157
+ editEndLine = span.endLine;
158
+ }
159
+ }
160
+
161
+ const meta = { source };
162
+ if (prevSnapshot && Array.isArray(prevSnapshot.ranges)) {
163
+ if (opts.shiftRanges === false) {
164
+ meta.ranges = prevSnapshot.ranges.map((r) => ({ ...r }));
165
+ } else {
166
+ const shifted = shiftSnapshotRangesForEdit(prevSnapshot, {
167
+ lineDelta,
168
+ editStartLine,
169
+ editEndLine,
170
+ });
171
+ if (shifted && Array.isArray(shifted.ranges)) meta.ranges = shifted.ranges;
172
+ }
173
+ }
174
+ if (snapshotMetaHasFullCoverage(meta, text)) {
175
+ meta.contentHash = hashText(text);
176
+ }
177
+ return meta;
178
+ }
179
+
180
+ export function maybeAutoStripLineNumberPrefixes(oldStr) {
181
+ if (typeof oldStr !== 'string' || oldStr.length === 0) return null;
182
+ if (!/^\s*\d+[\t│→]/.test(oldStr)) return null;
183
+ const lines = oldStr.split('\n');
184
+ const stripped = [];
185
+ for (const ln of lines) {
186
+ const m = ln.match(/^\s*\d+[\t│→](.*)$/);
187
+ if (!m) return null;
188
+ stripped.push(m[1]);
189
+ }
190
+ return stripped.join('\n');
191
+ }
192
+
193
+ export function countOccurrences(haystack, needle) {
194
+ if (typeof needle !== 'string' || needle.length === 0) return 0;
195
+ let count = 0;
196
+ let idx = 0;
197
+ while ((idx = haystack.indexOf(needle, idx)) !== -1) {
198
+ count++;
199
+ idx += needle.length;
200
+ }
201
+ return count;
202
+ }
203
+
204
+ export function lineContextAround(content, startLine, endLine, radius = 3, maxChars = 1600) {
205
+ const lines = String(content ?? '').split('\n');
206
+ const total = lines.length;
207
+ const start = Math.max(1, Math.min(total, startLine) - radius);
208
+ const end = Math.min(total, Math.max(startLine, endLine) + radius);
209
+ let out = lines
210
+ .slice(start - 1, end)
211
+ .map((line, i) => renderReadLine(start + i, line, { truncateLongLine: false }))
212
+ .join('\n');
213
+ if (out.length > maxChars) {
214
+ const head = out.slice(0, Math.floor(maxChars * 0.6));
215
+ const tail = out.slice(Math.max(0, out.length - Math.floor(maxChars * 0.4)));
216
+ out = `${head}\n... [context middle omitted] ...\n${tail}`;
217
+ }
218
+ return out;
219
+ }
220
+
221
+ export function compactEditContext(content, startLine, endLine, opts = {}) {
222
+ const lines = String(content ?? '').split('\n');
223
+ const total = Math.max(1, lines.length);
224
+ const maxLines = Math.max(1, Math.min(20, Number(opts.maxLines) || 20));
225
+ const targetStart = Math.max(1, Math.min(total, Number(startLine) || 1));
226
+ const targetEnd = Math.max(targetStart, Math.min(total, Number(endLine) || targetStart));
227
+ const targetLines = Math.max(1, targetEnd - targetStart + 1);
228
+ const extra = Math.max(0, maxLines - targetLines);
229
+ let start = Math.max(1, targetStart - Math.floor(extra / 2));
230
+ let end = Math.min(total, start + maxLines - 1);
231
+ start = Math.max(1, Math.min(start, Math.max(1, end - maxLines + 1)));
232
+ const range = { startLine: start, endLine: end };
233
+ const maxLineChars = Math.max(80, Math.min(240, Number(opts.maxLineChars) || 180));
234
+ let text = lines.slice(start - 1, end).map((line, i) => {
235
+ let s = String(line ?? '');
236
+ if (s.length > maxLineChars) s = `${s.slice(0, Math.max(20, maxLineChars - 24))} ... [line truncated]`;
237
+ return renderReadLine(start + i, s, { truncateLongLine: false });
238
+ }).join('\n');
239
+ const maxChars = Math.max(300, Math.min(1800, Number(opts.maxChars) || 1400));
240
+ if (text.length > maxChars) text = `${text.slice(0, maxChars - 28)}\n... [context truncated]`;
241
+ return { range, text };
242
+ }