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,126 @@
1
+ import { readFileSync } from 'fs';
2
+ import { findActualString } from '../edit-normalize.mjs';
3
+ import { normalizeOutputPath } from './path-utils.mjs';
4
+ import { READ_MAX_SIZE_BYTES } from './read-constants.mjs';
5
+ import { isBinaryFile } from './binary-file.mjs';
6
+ import { hashText } from './hash-utils.mjs';
7
+ import { rangeHashesForReadRanges } from './snapshot-helpers.mjs';
8
+ import {
9
+ compactEditContext,
10
+ countOccurrences,
11
+ lineContextAround,
12
+ } from './edit-context-utils.mjs';
13
+ import { findEditHint } from './edit-hint.mjs';
14
+ import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
15
+
16
+ export function recordPreviewSnapshot(fullPath, scope, content, range) {
17
+ if (!fullPath || !range) return false;
18
+ try {
19
+ recordReadSnapshot(fullPath, undefined, scope, {
20
+ source: 'edit_failure_preview',
21
+ ranges: [range],
22
+ rangeHashes: rangeHashesForReadRanges(content, [range]),
23
+ });
24
+ return true;
25
+ } catch {
26
+ return false;
27
+ }
28
+ }
29
+
30
+ export function editFailureContextHint(content, startLine, endLine, options = {}, meta = {}) {
31
+ if (options.includePreview === false) return '';
32
+ const rendered = compactEditContext(content, startLine, endLine, {
33
+ maxLines: options.previewMaxLines || 20,
34
+ maxChars: options.previewMaxChars || 1400,
35
+ });
36
+ const canRecord = options.recordPreviewSnapshot === true && meta.matchesLength === 1;
37
+ const recorded = canRecord
38
+ ? recordPreviewSnapshot(options.fullPath, options.scope, options.snapshotContent ?? content, rendered.range)
39
+ : false;
40
+ return `\n context ${rendered.range.startLine}-${rendered.range.endLine}${recorded ? ' (snapshot recorded)' : ''}:\n${rendered.text}`;
41
+ }
42
+
43
+ export function buildStaleEditRecovery({ fullPath, scope, oldStrings = [], recordPreviewSnapshot: shouldRecordPreview = false } = {}) {
44
+ let content = '';
45
+ try { content = readFileSync(fullPath, 'utf-8'); }
46
+ catch { return ''; }
47
+ const candidates = (Array.isArray(oldStrings) ? oldStrings : [])
48
+ .map((entry) => typeof entry === 'string' ? entry : entry?.old_string)
49
+ .filter((s) => typeof s === 'string' && s.length > 0)
50
+ .slice(0, 3);
51
+ for (const oldString of candidates) {
52
+ const actual = findActualString(content, oldString) || oldString;
53
+ const count = countOccurrences(content, actual);
54
+ if (count !== 1) continue;
55
+ const idx = content.indexOf(actual);
56
+ const startLine = lineForIndex(content, idx);
57
+ const endLine = startLine + actual.split('\n').length - 1;
58
+ return editFailureContextHint(content, startLine, endLine, {
59
+ fullPath,
60
+ scope,
61
+ snapshotContent: content,
62
+ recordPreviewSnapshot: shouldRecordPreview,
63
+ previewMaxLines: 12,
64
+ previewMaxChars: 1000,
65
+ }, { matchesLength: 1 });
66
+ }
67
+ if (candidates[0]) {
68
+ const hint = findEditHint(content, candidates[0], null);
69
+ if (hint) return `\n current file:${hint}`;
70
+ }
71
+ return '';
72
+ }
73
+
74
+ export function lineForIndex(content, index) {
75
+ if (index <= 0) return 1;
76
+ const end = Math.min(index, content.length);
77
+ let lineNo = 1;
78
+ for (let i = 0; i < end; i++) {
79
+ if (content.charCodeAt(i) === 10) lineNo++;
80
+ }
81
+ return lineNo;
82
+ }
83
+
84
+ export function primeReadSnapshotForEdit({ fullPath, filePath, st, scope, oldStrings = [], lineRange = null }) {
85
+ if (!/^(1|true|yes|on)$/i.test(String(process.env.MIXDOG_EDIT_AUTO_SNAPSHOT || ''))) return null;
86
+ if (!st || st.size > READ_MAX_SIZE_BYTES || isBinaryFile(fullPath, st.size)) return null;
87
+ let rawBuf;
88
+ try { rawBuf = readFileSync(fullPath); }
89
+ catch { return null; }
90
+ if (!Buffer.isBuffer(rawBuf)) return null;
91
+ const content = rawBuf.toString('utf-8');
92
+ const lines = content.split('\n');
93
+ recordReadSnapshot(fullPath, st, scope, {
94
+ source: 'auto_snapshot',
95
+ contentHash: hashText(content),
96
+ });
97
+
98
+ const out = [
99
+ `Edit snapshot primed from disk for ${normalizeOutputPath(filePath)} (no prior read in this scope).`,
100
+ ];
101
+ const checks = [];
102
+ let firstContext = null;
103
+ for (let i = 0; i < Math.min(oldStrings.length, 5); i++) {
104
+ const entry = oldStrings[i] || {};
105
+ const label = entry.label || `edit ${i}`;
106
+ const oldString = entry.old_string;
107
+ if (typeof oldString !== 'string' || oldString.length === 0) continue;
108
+ const count = countOccurrences(content, oldString);
109
+ checks.push(`${label}: old_string ${count === 1 ? 'found once' : count === 0 ? 'not found' : `found ${count} times`}`);
110
+ if (!firstContext && count > 0) {
111
+ const idx = content.indexOf(oldString);
112
+ const startLine = lineForIndex(content, idx);
113
+ const endLine = startLine + oldString.split('\n').length - 1;
114
+ firstContext = lineContextAround(content, startLine, endLine);
115
+ }
116
+ }
117
+ if (checks.length > 0) out.push(`Match check: ${checks.join('; ')}`);
118
+ if (lineRange) {
119
+ out.push(`Line check: requested ${lineRange.startLine}-${lineRange.endLine}; file has ${lines.length} lines.`);
120
+ out.push(`Context around requested lines:\n${lineContextAround(content, lineRange.startLine, lineRange.endLine)}`);
121
+ } else if (firstContext) {
122
+ out.push(`Context around first match:\n${firstContext}`);
123
+ }
124
+ const diagnostic = out.join('\n');
125
+ return { content, rawBuf, diagnostic: diagnostic || undefined };
126
+ }
@@ -0,0 +1,141 @@
1
+ import { snapshotCoversFullFile } from './snapshot-helpers.mjs';
2
+ import {
3
+ firstDivergence,
4
+ renderCodepointPreview,
5
+ describeFirstDivergence,
6
+ renderCharForDiff,
7
+ } from './edit-diagnostics.mjs';
8
+ import { editFailureSuffix } from './edit-match-utils.mjs';
9
+
10
+ function leadingWhitespaceColumns(line) {
11
+ let cols = 0;
12
+ for (const ch of String(line ?? '')) {
13
+ if (ch === ' ') cols++;
14
+ else if (ch === '\t') cols += 4;
15
+ else break;
16
+ }
17
+ return cols;
18
+ }
19
+
20
+ function formatIndentKind(line) {
21
+ const raw = /^[ \t]*/.exec(String(line ?? ''))?.[0] || '';
22
+ if (!raw) return 'none';
23
+ const spaces = (raw.match(/ /g) || []).length;
24
+ const tabs = (raw.match(/\t/g) || []).length;
25
+ return [spaces ? `${spaces} spaces` : '', tabs ? `${tabs} tabs` : ''].filter(Boolean).join(' + ');
26
+ }
27
+
28
+ function indentMismatchHint(sentLine, fileLine) {
29
+ return '';
30
+ }
31
+
32
+ function firstLineFallbackStageHint(content, line) {
33
+ return '';
34
+ }
35
+
36
+ function editRetryCallHint(options, candidate) {
37
+ return '';
38
+ }
39
+
40
+ function editNearestReadHint(options, line) {
41
+ return '';
42
+ }
43
+
44
+ // Lightweight nearest-match hint for `Error [code 8]: old_string not
45
+ // found`. Probes by the first non-empty line of `old_string` (trimmed,
46
+ // capped at 60 chars then 30) so callers see where they likely meant
47
+ // to land. Substring only, no fuzzy diff, to keep the failure path cheap.
48
+ export function findEditHint(content, oldStr, snapshot = null, options = {}) {
49
+ const oldLines = String(oldStr || '').split(/\r?\n/);
50
+ const firstNonEmptyIndex = oldLines.findIndex((l) => l.trim().length > 0);
51
+ const firstNonEmpty = firstNonEmptyIndex >= 0 ? oldLines[firstNonEmptyIndex] : '';
52
+ const trimmed = firstNonEmpty.trim();
53
+ const editIdx = Number.isInteger(options?.editIndex) ? options.editIndex : null;
54
+ const idxTag = editIdx !== null ? ` [edit ${editIdx}]` : '';
55
+ if (trimmed.length < 8) {
56
+ // No probe long enough to anchor a nearest-match search. Still emit
57
+ // an invariant divergence line so the caller knows where the closest
58
+ // prefix landed instead of getting silence.
59
+ const div = describeFirstDivergence(content, oldStr);
60
+ if (!div) return '';
61
+ return `${idxTag ? `\n edit ${editIdx} miss:` : ''}`
62
+ + `\n diverge${idxTag}: at line ${div.line} col ${div.col} expected ${renderCharForDiff(div.expected)} but file has ${renderCharForDiff(div.found)} (common prefix ${div.prefixLen} chars from line ${div.startLine} col ${div.startCol})`;
63
+ }
64
+ const probes = [trimmed.slice(0, 60), trimmed.slice(0, 30)].filter((p) => p.length >= 8);
65
+ const lines = String(content).split('\n');
66
+
67
+ let normHint = '';
68
+ try {
69
+ const rawContent = String(content);
70
+ const rawOld = String(oldStr);
71
+ if (rawContent.indexOf(rawOld) === -1) {
72
+ const nfcContent = rawContent.normalize('NFC');
73
+ const nfcOld = rawOld.normalize('NFC');
74
+ const nfdContent = rawContent.normalize('NFD');
75
+ const nfdOld = rawOld.normalize('NFD');
76
+ if (nfcOld !== rawOld && nfcContent.indexOf(nfcOld) !== -1) {
77
+ normHint = ' Unicode normalisation mismatch: NFC-normalising old_string matches the file. Re-send old_string in NFC form (e.g. JS `s.normalize("NFC")`).';
78
+ } else if (nfdOld !== rawOld && nfdContent.indexOf(nfdOld) !== -1 && nfcContent.indexOf(nfcOld) === -1) {
79
+ normHint = ' Unicode normalisation mismatch: NFD form of old_string matches the file but NFC does not. The file likely stores NFD; re-send old_string in NFD form.';
80
+ }
81
+ }
82
+ } catch {}
83
+
84
+ let winStart = 1;
85
+ let winEnd = lines.length;
86
+ if (snapshot && !snapshotCoversFullFile(snapshot)) {
87
+ const ranges = Array.isArray(snapshot.ranges) ? snapshot.ranges : [];
88
+ if (ranges.length === 0) return normHint;
89
+ winStart = ranges[0].startLine;
90
+ const last = ranges[ranges.length - 1];
91
+ winEnd = last.endLine === Infinity ? lines.length : Math.min(lines.length, last.endLine);
92
+ }
93
+ for (const probe of probes) {
94
+ for (let i = winStart - 1; i < winEnd; i++) {
95
+ if (lines[i] !== undefined && lines[i].includes(probe)) {
96
+ const preview = lines[i].length > 80 ? lines[i].slice(0, 77) + '...' : lines[i];
97
+ const sentPreview = renderCodepointPreview(firstNonEmpty, 40);
98
+ const filePreview = renderCodepointPreview(lines[i], 40);
99
+ const probeIdx = lines[i].indexOf(probe);
100
+ const sliceForDiff = probeIdx >= 0 ? lines[i].slice(probeIdx, probeIdx + trimmed.length) : lines[i];
101
+ const div = firstDivergence(trimmed, sliceForDiff);
102
+ // Translate the intra-slice char offset back into absolute
103
+ // file line+col so the operator can jump straight to the
104
+ // mismatch. lines[] is split on \n, so col is 1-based within
105
+ // the line. The probe sits at probeIdx (0-based) inside the
106
+ // nearest line; the divergence sits div.index codepoints
107
+ // further along. Fall back to `describeFirstDivergence` when
108
+ // the probe-vs-trimmed compare reports no divergence (i.e.
109
+ // probe is a true prefix of the slice) — we still want a
110
+ // global coordinate against the full old_string.
111
+ let divLine = '';
112
+ if (div) {
113
+ const lineNo = i + 1;
114
+ const col = (probeIdx >= 0 ? probeIdx : 0) + div.index + 1;
115
+ divLine = `\n diverge${idxTag}: at line ${lineNo} col ${col} expected ${renderCharForDiff(div.expected)} but file has ${renderCharForDiff(div.found)}`;
116
+ } else {
117
+ const globalDiv = describeFirstDivergence(content, oldStr);
118
+ if (globalDiv && globalDiv.prefixLen < String(oldStr).length) {
119
+ divLine = `\n diverge${idxTag}: at line ${globalDiv.line} col ${globalDiv.col} expected ${renderCharForDiff(globalDiv.expected)} but file has ${renderCharForDiff(globalDiv.found)} (common prefix ${globalDiv.prefixLen} chars from line ${globalDiv.startLine} col ${globalDiv.startCol})`;
120
+ }
121
+ }
122
+ return ` Nearest match${idxTag} at line ${i + 1}: ${JSON.stringify(preview)}.${normHint}`
123
+ + `\n sent : ${sentPreview}`
124
+ + `\n file : ${filePreview}`
125
+ + divLine
126
+ + indentMismatchHint(firstNonEmpty, lines[i])
127
+ + firstLineFallbackStageHint(content, firstNonEmpty)
128
+ + editNearestReadHint(options, i + 1)
129
+ + editFailureSuffix(content, oldStr);
130
+ }
131
+ }
132
+ }
133
+ // Invariant fallback: no probe landed, but the operator still needs a
134
+ // file coordinate. Run the longest-common-prefix scan against the full
135
+ // old_string so the miss is debuggable rather than silent.
136
+ const globalDiv = describeFirstDivergence(content, oldStr);
137
+ const globalLine = globalDiv && globalDiv.prefixLen < String(oldStr).length
138
+ ? `\n diverge${idxTag}: at line ${globalDiv.line} col ${globalDiv.col} expected ${renderCharForDiff(globalDiv.expected)} but file has ${renderCharForDiff(globalDiv.found)} (common prefix ${globalDiv.prefixLen} chars from line ${globalDiv.startLine} col ${globalDiv.startCol})`
139
+ : '';
140
+ return normHint + globalLine + editFailureSuffix(content, oldStr);
141
+ }
@@ -0,0 +1,194 @@
1
+ export function editFailureSuffix(content, oldStr) {
2
+ return '';
3
+ }
4
+
5
+ export function validateEditChunkSize(oldStr, replaceAll, allowLarge) {
6
+ if (replaceAll || allowLarge) return null;
7
+ const lines = String(oldStr || '').split(/\r?\n/).length;
8
+ if (lines < 30) return null;
9
+ return `Error [code 10]: old_string is ${lines} lines (>= 30).`;
10
+ }
11
+
12
+ export function occurrenceLinesPlain(content, needle, max = 3) {
13
+ if (typeof needle !== 'string' || needle.length === 0) return [];
14
+ const lines = [];
15
+ let pos = 0, scanned = 0, lineNo = 1;
16
+ while (lines.length < max) {
17
+ const idx = content.indexOf(needle, pos);
18
+ if (idx === -1) break;
19
+ for (let i = scanned; i < idx; i++) {
20
+ if (content.charCodeAt(i) === 10) lineNo++;
21
+ }
22
+ scanned = idx;
23
+ lines.push(lineNo);
24
+ pos = idx + needle.length;
25
+ }
26
+ return lines;
27
+ }
28
+
29
+ export function occurrenceLinesCrlf(content, ranges, max = 3) {
30
+ if (!Array.isArray(ranges) || ranges.length === 0) return [];
31
+ const lines = [];
32
+ let scanned = 0, lineNo = 1;
33
+ for (let k = 0; k < Math.min(max, ranges.length); k++) {
34
+ const idx = ranges[k].start;
35
+ for (let i = scanned; i < idx; i++) {
36
+ if (content.charCodeAt(i) === 10) lineNo++;
37
+ }
38
+ scanned = idx;
39
+ lines.push(lineNo);
40
+ }
41
+ return lines;
42
+ }
43
+
44
+ export function formatMatchLines(linesArr, totalCount) {
45
+ if (linesArr.length === 0) return '';
46
+ const more = totalCount > linesArr.length ? ` (+${totalCount - linesArr.length} more)` : '';
47
+ return ` Matches at lines: ${linesArr.join(', ')}${more}.`;
48
+ }
49
+
50
+ export function buildCrlfNormalisedViewWithMap(text) {
51
+ const source = String(text ?? '');
52
+ let normalised = '';
53
+ const map = [];
54
+ for (let i = 0; i < source.length;) {
55
+ map[normalised.length] = i;
56
+ if (source[i] === '\r' && source[i + 1] === '\n') {
57
+ normalised += '\n';
58
+ i += 2;
59
+ } else {
60
+ normalised += source[i];
61
+ i += 1;
62
+ }
63
+ }
64
+ map[normalised.length] = source.length;
65
+ return { normalised, map };
66
+ }
67
+
68
+ export function findCrlfNormalisedMatches(content, oldStr) {
69
+ if (typeof oldStr !== 'string' || oldStr.length === 0) return null;
70
+ if (String(content).indexOf('\r\n') === -1 && oldStr.indexOf('\r\n') === -1) return null;
71
+ const { normalised, map } = buildCrlfNormalisedViewWithMap(content);
72
+ const normalisedOld = oldStr.replace(/\r\n/g, '\n');
73
+ if (normalisedOld.length === 0 || normalisedOld === oldStr && normalised === content) return null;
74
+ const ranges = [];
75
+ let idx = 0;
76
+ while ((idx = normalised.indexOf(normalisedOld, idx)) !== -1) {
77
+ ranges.push({
78
+ normStart: idx,
79
+ normEnd: idx + normalisedOld.length,
80
+ start: map[idx],
81
+ end: map[idx + normalisedOld.length],
82
+ });
83
+ idx += normalisedOld.length;
84
+ }
85
+ return { normalised, normalisedOld, ranges };
86
+ }
87
+
88
+ export function replacementForOriginalSlice(newStr, originalSlice, fileContent) {
89
+ if (typeof newStr !== 'string') return newStr;
90
+ const slice = String(originalSlice);
91
+ if (!slice.includes('\r\n') && !slice.includes('\n')) {
92
+ if (typeof fileContent !== 'string') return newStr;
93
+ if (fileContent.indexOf('\r\n') === -1) return newStr;
94
+ // Pure-CRLF file: every bare \n is part of a \r\n pair. Mixed
95
+ // files (any bare \n that is not preceded by \r) stay untouched
96
+ // so we don't synthesise CRLF where the file uses LF.
97
+ for (let i = 0; i < fileContent.length; i += 1) {
98
+ if (fileContent.charCodeAt(i) === 10 && (i === 0 || fileContent.charCodeAt(i - 1) !== 13)) {
99
+ return newStr;
100
+ }
101
+ }
102
+ return newStr.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n');
103
+ }
104
+ const lfReplacement = newStr.replace(/\r\n/g, '\n');
105
+ let result = slice.includes('\r\n')
106
+ ? lfReplacement.replace(/\n/g, '\r\n')
107
+ : lfReplacement;
108
+ // A matched slice can end on the last line's trailing EOL CR (split
109
+ // CRLF) while newStr — typically copied from a \r-stripped read view —
110
+ // has none. If the replacement ends mid-line, re-attach that consumed
111
+ // CR so the following LF still forms a \r\n instead of silently
112
+ // degrading to a lone \n (mixed-EOL corruption). A slice ending in a
113
+ // bare CR is always a split CRLF; the end-of-line guard avoids
114
+ // appending a stray CR when the replacement already carries its own
115
+ // line ending.
116
+ if (slice.endsWith('\r') && !/[\r\n]$/.test(result)) result += '\r';
117
+ return result;
118
+ }
119
+
120
+ export function replaceRangesFromOriginal(content, ranges, newStr) {
121
+ if (!Array.isArray(ranges) || ranges.length === 0) return content;
122
+ const sorted = ranges.slice().sort((a, b) => a.start - b.start);
123
+ let cursor = 0;
124
+ let out = '';
125
+ for (const range of sorted) {
126
+ if (!range || range.start < cursor || range.end < range.start) continue;
127
+ out += content.slice(cursor, range.start);
128
+ const originalSlice = content.slice(range.start, range.end);
129
+ out += replacementForOriginalSlice(newStr, originalSlice);
130
+ cursor = range.end;
131
+ }
132
+ return out + content.slice(cursor);
133
+ }
134
+
135
+ export function replaceSingleLiteralAt(content, index, needle, replacement) {
136
+ return content.slice(0, index) + replacement + content.slice(index + needle.length);
137
+ }
138
+
139
+ export function findLiteralOccurrenceState(haystack, needle) {
140
+ if (!needle) return { count: 0, index: -1 };
141
+ const first = haystack.indexOf(needle);
142
+ if (first === -1) return { count: 0, index: -1 };
143
+ // Overlap-aware ambiguity: `aa` in `aaa` has a second hit at first+1,
144
+ // not only at first+needle.length (which misses overlapping pairs).
145
+ const second = haystack.indexOf(needle, first + 1);
146
+ if (second === -1) return { count: 1, index: first };
147
+ return { count: 2, index: first };
148
+ }
149
+
150
+ export function countLiteralOccurrences(haystack, needle, limit = Infinity) {
151
+ if (!needle) return 0;
152
+ let count = 0;
153
+ let idx = 0;
154
+ while ((idx = haystack.indexOf(needle, idx)) !== -1) {
155
+ count++;
156
+ if (count >= limit) return count;
157
+ idx += 1;
158
+ }
159
+ return count;
160
+ }
161
+
162
+ export function diagnoseOtherEdits(content, edits, failedIndex) {
163
+ if (!Array.isArray(edits) || edits.length <= 1) return '';
164
+ let okCount = 0;
165
+ const problems = [];
166
+ for (let j = 0; j < edits.length; j++) {
167
+ if (j === failedIndex) continue;
168
+ const e = edits[j];
169
+ if (!e || typeof e.old_string !== 'string' || typeof e.new_string !== 'string') {
170
+ problems.push(`${j}=invalid`); continue;
171
+ }
172
+ const oldStr = e.old_string;
173
+ if (oldStr.length === 0) { problems.push(`${j}=empty`); continue; }
174
+ const cnt = countLiteralOccurrences(content, oldStr);
175
+ if (cnt === 1) { okCount++; continue; }
176
+ if (cnt > 1) {
177
+ if (e.replace_all === true) okCount++;
178
+ else problems.push(`${j}=ambig(${cnt})`);
179
+ continue;
180
+ }
181
+ const crlf = findCrlfNormalisedMatches(content, oldStr);
182
+ const crlfCnt = crlf ? crlf.ranges.length : 0;
183
+ if (crlfCnt === 1) { okCount++; continue; }
184
+ if (crlfCnt > 1) {
185
+ if (e.replace_all === true) okCount++;
186
+ else problems.push(`${j}=ambig(${crlfCnt})`);
187
+ continue;
188
+ }
189
+ problems.push(`${j}=miss`);
190
+ }
191
+ if (problems.length === 0) return '';
192
+ const head = okCount > 0 ? `${okCount} ok, ` : '';
193
+ return `\n Peers: ${head}${problems.join(', ')}`;
194
+ }
@@ -0,0 +1,60 @@
1
+ import { openSync, writeSync, closeSync, fsyncSync, statSync } from 'fs';
2
+ import { normalizeErrorMessage } from './path-diagnostics.mjs';
3
+ import { hashText } from './hash-utils.mjs';
4
+ import { partialByteWriteEnabled } from './edit-byte-utils.mjs';
5
+ import { atomicWriteShouldFsync } from './atomic-write.mjs';
6
+
7
+ function partialWriteHooks(hooks = {}) {
8
+ return {
9
+ ioTraceStart: typeof hooks.ioTraceStart === 'function' ? hooks.ioTraceStart : () => 0,
10
+ ioTraceDone: typeof hooks.ioTraceDone === 'function' ? hooks.ioTraceDone : () => {},
11
+ validatePreparedEditBase: typeof hooks.validatePreparedEditBase === 'function' ? hooks.validatePreparedEditBase : () => null,
12
+ };
13
+ }
14
+
15
+ export function tryWriteSameSizeByteReplacementsSync(fullPath, replacements, { baseStatSnapshot, baseMutationGeneration, baseContentHash, contentHash, fsync, filePath } = {}, hooks = {}) {
16
+ const { ioTraceStart, ioTraceDone, validatePreparedEditBase } = partialWriteHooks(hooks);
17
+ if (!partialByteWriteEnabled()) return null;
18
+ if (!Array.isArray(replacements) || replacements.length === 0 || !baseStatSnapshot) return null;
19
+ const sorted = replacements.slice().sort((a, b) => a.start - b.start || a.end - b.end);
20
+ for (let i = 0; i < sorted.length; i++) {
21
+ const span = sorted[i];
22
+ if (!span || !Buffer.isBuffer(span.newBytes)) return null;
23
+ if (!Number.isFinite(span.start) || !Number.isFinite(span.end) || span.start < 0 || span.end < span.start) return null;
24
+ if (span.end - span.start !== span.newBytes.length) return null;
25
+ if (i > 0 && span.start < sorted[i - 1].end) return null;
26
+ }
27
+ const prewriteErr = validatePreparedEditBase({
28
+ fullPath,
29
+ filePath: filePath || fullPath,
30
+ baseStatSnapshot,
31
+ baseMutationGeneration,
32
+ baseContentHash,
33
+ });
34
+ if (prewriteErr) return { ok: false, error: prewriteErr };
35
+ const traceStart = ioTraceStart();
36
+ let fd = null;
37
+ try {
38
+ fd = openSync(fullPath, 'r+');
39
+ for (const span of sorted) {
40
+ writeSync(fd, span.newBytes, 0, span.newBytes.length, span.start);
41
+ }
42
+ if (atomicWriteShouldFsync(fsync)) {
43
+ try { fsyncSync(fd); } catch (err) {
44
+ if (!['EPERM', 'ENOTSUP', 'EINVAL'].includes(err?.code)) throw err;
45
+ }
46
+ }
47
+ } catch (err) {
48
+ return { ok: false, error: `Error: partial byte write failed — ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}` };
49
+ } finally {
50
+ try { if (fd !== null) closeSync(fd); } catch {}
51
+ }
52
+ let stat = null;
53
+ try { stat = statSync(fullPath); } catch {}
54
+ ioTraceDone('edit_partial_write', traceStart, {
55
+ pathHash: hashText(fullPath).slice(0, 12),
56
+ replacements: sorted.length,
57
+ bytes: sorted.reduce((sum, span) => sum + span.newBytes.length, 0),
58
+ });
59
+ return { ok: true, stat, contentHash };
60
+ }