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,98 @@
1
+ import { hashText } from './hash-utils.mjs';
2
+ import {
3
+ normaliseRangeHashEntry,
4
+ statMatchesSnapshot,
5
+ } from './snapshot-helpers.mjs';
6
+
7
+ function snapshotRangeHashRows(snapshot) {
8
+ return Array.isArray(snapshot?.rangeHashes)
9
+ ? snapshot.rangeHashes
10
+ : (snapshot?.rangeHash && Array.isArray(snapshot.ranges) && snapshot.ranges.length > 0
11
+ ? [{ ...snapshot.ranges[0], hash: snapshot.rangeHash }]
12
+ : []);
13
+ }
14
+
15
+ export function isSnapshotStale(stat, snapshot, { fullPath = '', readCache = null, readTextForSnapshotCheck } = {}) {
16
+ // Unified structure (mirrors edit's validatePreparedEditBase):
17
+ // stat-match FIRST → fail-closed-on-no-material → hash-verify.
18
+ // Fast-path: a full stat match (size + mtime±1 + ctime±1) ⇒ untouched
19
+ // → not stale, accept without reading. statMatchesSnapshot returns
20
+ // false on a missing/incomplete snapshot, so those fall through (no
21
+ // fail-open). Tradeoff (accepted policy): a size-preserving write that
22
+ // ALSO restores mtime AND ctime is not caught — vanishingly rare,
23
+ // ctime is not userland-settable on the usual platforms.
24
+ if (statMatchesSnapshot(stat, snapshot)) return false;
25
+ // Stat drifted (or incomplete snapshot) → content hash is the gate.
26
+ const rangeHashRows = snapshotRangeHashRows(snapshot);
27
+ const hasHashMaterial = !!snapshot.contentHash || rangeHashRows.length > 0;
28
+ // Fail-closed: no integrity material AND stat already drifted ⇒ cannot
29
+ // verify against current bytes, treat as stale (force re-read).
30
+ if (!hasHashMaterial) return true;
31
+ const canReadContent = fullPath && typeof readTextForSnapshotCheck === 'function';
32
+ if (!canReadContent) return true;
33
+ // Refresh the snapshot's stat fields to the live values once the hash
34
+ // confirms bytes are identical — silences future false positives for
35
+ // the same mtime-only churn.
36
+ const refreshSnapshotStat = () => {
37
+ if (Number.isFinite(stat.mtimeMs)) snapshot.mtimeMs = stat.mtimeMs;
38
+ if (typeof stat.size === 'number') snapshot.size = stat.size;
39
+ if (Number.isFinite(stat.ctimeMs)) snapshot.ctimeMs = stat.ctimeMs;
40
+ };
41
+ if (snapshot.contentHash) {
42
+ try {
43
+ const cur = readTextForSnapshotCheck(fullPath, readCache, stat);
44
+ if (hashText(cur) !== snapshot.contentHash) return true;
45
+ refreshSnapshotStat();
46
+ return false;
47
+ } catch {
48
+ // Unreadable / stat race — cannot verify, treat as stale.
49
+ return true;
50
+ }
51
+ }
52
+ // rangeHashes-only path (paged reads).
53
+ try {
54
+ const raw = readTextForSnapshotCheck(fullPath, readCache, stat);
55
+ const lines = raw.split(/\r?\n/);
56
+ let verified = 0;
57
+ for (const row of rangeHashRows) {
58
+ const r = normaliseRangeHashEntry(row);
59
+ if (!r) continue;
60
+ const startIdx = Math.max(0, (r.startLine || 1) - 1);
61
+ const endIdx = r.endLine === Infinity ? lines.length : Math.min(lines.length, r.endLine);
62
+ const rangeText = lines.slice(startIdx, endIdx).join('\n');
63
+ if (hashText(rangeText) !== r.hash) return true;
64
+ verified++;
65
+ }
66
+ // Fail-closed: rangeHashes present but no row could be verified
67
+ // (all malformed) ⇒ cannot prove identity, treat as stale.
68
+ if (verified === 0) return true;
69
+ refreshSnapshotStat();
70
+ return false;
71
+ } catch {
72
+ return true;
73
+ }
74
+ }
75
+
76
+ export function readContentIfSnapshotHashMatches(fullPath, snapshot, { readCache = null, st = null, readTextForSnapshotCheck } = {}) {
77
+ if (!snapshot || typeof readTextForSnapshotCheck !== 'function') return null;
78
+ try {
79
+ const content = readTextForSnapshotCheck(fullPath, readCache, st);
80
+ if (snapshot.contentHash) {
81
+ return hashText(content) === snapshot.contentHash ? content : null;
82
+ }
83
+ const rangeHashRows = snapshotRangeHashRows(snapshot);
84
+ if (rangeHashRows.length === 0) return null;
85
+ const lines = content.split(/\r?\n/);
86
+ for (const row of rangeHashRows) {
87
+ const r = normaliseRangeHashEntry(row);
88
+ if (!r) return null;
89
+ const startIdx = Math.max(0, (r.startLine || 1) - 1);
90
+ const endIdx = r.endLine === Infinity ? lines.length : Math.min(lines.length, r.endLine);
91
+ const rangeText = lines.slice(startIdx, endIdx).join('\n');
92
+ if (hashText(rangeText) !== r.hash) return null;
93
+ }
94
+ return content;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
@@ -0,0 +1,69 @@
1
+ import { createReadStream } from 'fs';
2
+
3
+ function isJsWhitespaceCodeUnit(code) {
4
+ return code === 32
5
+ || (code >= 9 && code <= 13)
6
+ || code === 0x00A0
7
+ || code === 0x1680
8
+ || (code >= 0x2000 && code <= 0x200A)
9
+ || code === 0x2028
10
+ || code === 0x2029
11
+ || code === 0x202F
12
+ || code === 0x205F
13
+ || code === 0x3000
14
+ || code === 0xFEFF;
15
+ }
16
+
17
+ async function countTextStatsStreamingUtf8(fullPath, size) {
18
+ if (!size) return { lines: 0, words: 0, bytes: 0 };
19
+ const stream = createReadStream(fullPath, { encoding: 'utf-8', highWaterMark: 1024 * 1024 });
20
+ let lines = 0;
21
+ let words = 0;
22
+ let inWord = false;
23
+ let lastChar = '';
24
+ for await (const chunk of stream) {
25
+ if (!chunk) continue;
26
+ for (let i = 0; i < chunk.length; i++) {
27
+ const code = chunk.charCodeAt(i);
28
+ if (code === 10) lines++;
29
+ if (isJsWhitespaceCodeUnit(code)) {
30
+ inWord = false;
31
+ } else if (!inWord) {
32
+ words++;
33
+ inWord = true;
34
+ }
35
+ lastChar = code;
36
+ }
37
+ }
38
+ if (lastChar && lastChar !== 10) lines++;
39
+ return { lines, words, bytes: size };
40
+ }
41
+
42
+ export async function countTextStatsStreaming(fullPath, size) {
43
+ if (!size) return { lines: 0, words: 0, bytes: 0 };
44
+ const stream = createReadStream(fullPath, { highWaterMark: 1024 * 1024 });
45
+ let lines = 0;
46
+ let words = 0;
47
+ let inWord = false;
48
+ let lastByte = -1;
49
+ for await (const chunk of stream) {
50
+ if (!chunk) continue;
51
+ for (let i = 0; i < chunk.length; i++) {
52
+ const b = chunk[i];
53
+ if (b >= 0x80) {
54
+ stream.destroy();
55
+ return countTextStatsStreamingUtf8(fullPath, size);
56
+ }
57
+ if (b === 10) lines++;
58
+ if (b === 32 || (b >= 9 && b <= 13)) {
59
+ inWord = false;
60
+ } else if (!inWord) {
61
+ words++;
62
+ inWord = true;
63
+ }
64
+ lastByte = b;
65
+ }
66
+ }
67
+ if (lastByte !== -1 && lastByte !== 10) lines++;
68
+ return { lines, words, bytes: size };
69
+ }
@@ -0,0 +1,23 @@
1
+ // Windows program-root derivation from present environment only. No guessed
2
+ // defaults: every candidate path must originate from a DEFINED env root, so a
3
+ // machine that lacks a given root simply contributes no candidates rather than
4
+ // inviting a hardcoded 'C:\\...' fallback.
5
+
6
+ // De-duped list of defined program-root dirs, in priority order. Undefined or
7
+ // empty env vars are dropped — never substituted with a literal default.
8
+ export function windowsProgramRoots() {
9
+ const roots = [
10
+ process.env.ProgramW6432,
11
+ process.env.ProgramFiles,
12
+ process.env['ProgramFiles(x86)'],
13
+ process.env.LOCALAPPDATA,
14
+ ].filter(Boolean)
15
+ return [...new Set(roots)]
16
+ }
17
+
18
+ // Windows system root (e.g. the dir holding System32). Returns undefined when
19
+ // SystemRoot is absent — callers must treat that as "not detected", never as
20
+ // 'C:\\Windows'.
21
+ export function windowsSystemRoot() {
22
+ return process.env.SystemRoot
23
+ }
@@ -0,0 +1,401 @@
1
+ import { mkdirSync, statSync, lstatSync, openSync, readSync, closeSync } from 'fs';
2
+ import { dirname } from 'path';
3
+ import { markCodeGraphDirtyPaths } from '../code-graph.mjs';
4
+ import {
5
+ normalizeInputPath,
6
+ normalizeOutputPath,
7
+ resolveAgainstCwd,
8
+ } from './path-utils.mjs';
9
+ import { normalizeErrorMessage } from './path-diagnostics.mjs';
10
+ import { withPathLock, withBuiltinPathLocks, pathLockKey } from './path-locks.mjs';
11
+ import { withAdvisoryLocks } from './advisory-lock.mjs';
12
+ import { hashText } from './hash-utils.mjs';
13
+ import { snapshotCoversFullFile } from './snapshot-helpers.mjs';
14
+ import {
15
+ getReadSnapshot,
16
+ isSnapshotStale,
17
+ readContentIfSnapshotHashMatches,
18
+ recordReadSnapshot,
19
+ } from './read-snapshot-runtime.mjs';
20
+ import {
21
+ invalidateBuiltinResultCache,
22
+ seedRawContentCacheAfterWrite,
23
+ } from './cache-layers.mjs';
24
+ import { atomicWrite } from './atomic-write.mjs';
25
+ import {
26
+ hasUnsafeWin32Component,
27
+ isWindowsDevicePath,
28
+ isUncPath,
29
+ isSpecialFileStat,
30
+ } from './device-paths.mjs';
31
+ import { assertPathReachable, assertPathsReachable } from './fs-reachability.mjs';
32
+
33
+ const STREAMING_THRESHOLD_BYTES = 1024 * 1024;
34
+
35
+ // BOM invariant: an existing file's encoding is determined STRICTLY by its
36
+ // leading BOM bytes — never by content sniffing. Mirrors Claude Code
37
+ // detectFileEncoding (src/utils/file.ts:84-118) and the FF FE -> utf16le
38
+ // check in src/utils/fileRead.ts:34. Returns a tag describing how to
39
+ // re-emit content so an overwrite preserves the on-disk encoding:
40
+ // { encoding: 'utf16le', bom: <Buffer FF FE> } -> UTF-16LE w/ BOM
41
+ // { encoding: 'utf8', bom: <Buffer EF BB BF> } -> UTF-8 w/ BOM
42
+ // { encoding: 'utf8', bom: null } -> UTF-8 (new/absent/no BOM)
43
+ export function detectExistingEncoding(fullPath) {
44
+ let fd;
45
+ try {
46
+ fd = openSync(fullPath, 'r');
47
+ const head = Buffer.alloc(3);
48
+ const bytesRead = readSync(fd, head, 0, 3, 0);
49
+ if (bytesRead >= 2 && head[0] === 0xff && head[1] === 0xfe) {
50
+ return { encoding: 'utf16le', bom: Buffer.from([0xff, 0xfe]) };
51
+ }
52
+ if (bytesRead >= 3 && head[0] === 0xef && head[1] === 0xbb && head[2] === 0xbf) {
53
+ return { encoding: 'utf8', bom: Buffer.from([0xef, 0xbb, 0xbf]) };
54
+ }
55
+ return { encoding: 'utf8', bom: null };
56
+ } catch {
57
+ // File absent or unreadable -> new file: utf-8, no BOM.
58
+ return { encoding: 'utf8', bom: null };
59
+ } finally {
60
+ if (fd !== undefined) { try { closeSync(fd); } catch {} }
61
+ }
62
+ }
63
+
64
+ export function toWriteBuffer(content, encoding = { encoding: 'utf8', bom: null }) {
65
+ if (typeof content !== 'string') return content;
66
+ const body = Buffer.from(content, encoding.encoding === 'utf16le' ? 'utf16le' : 'utf-8');
67
+ return encoding.bom ? Buffer.concat([encoding.bom, body]) : body;
68
+ }
69
+
70
+ function captureTargetSnapshot(fullPath) {
71
+ try {
72
+ const st = statSync(fullPath);
73
+ return {
74
+ exists: true,
75
+ size: st.size,
76
+ mtimeMs: Number(st.mtimeMs),
77
+ ctimeMs: Number(st.ctimeMs),
78
+ ino: Number(st.ino),
79
+ };
80
+ } catch (err) {
81
+ if (err && err.code === 'ENOENT') return { exists: false };
82
+ throw err;
83
+ }
84
+ }
85
+
86
+ /** Whole-file overwrite proof: full coverage sentinel + on-disk contentHash. */
87
+ function snapshotAllowsWholeFileOverwrite(snapshot) {
88
+ if (!snapshot) return false;
89
+ if (typeof snapshot.contentHash !== 'string' || !snapshot.contentHash) return false;
90
+ return snapshotCoversFullFile(snapshot);
91
+ }
92
+
93
+ function writeUncRejectMessage(displayPath, paths, { batch = false } = {}) {
94
+ for (const p of paths) {
95
+ if (typeof p !== 'string') continue;
96
+ if (typeof isUncPath === 'function' ? isUncPath(p) : (p.startsWith('\\\\') || p.startsWith('//'))) {
97
+ return batch
98
+ ? `FAIL ${normalizeOutputPath(displayPath)}: UNC/SMB paths are not supported (R1: NTLM-leak prevention)`
99
+ : `Error: UNC/SMB paths are not supported (R1: NTLM-leak prevention): ${displayPath}`;
100
+ }
101
+ }
102
+ return null;
103
+ }
104
+
105
+ function validateOverwriteAllowed(fullPath, displayPath, readStateScope, { batch = false, allowUnreadOverwrite = false } = {}) {
106
+ const uncErr = writeUncRejectMessage(displayPath, [fullPath], { batch });
107
+ if (uncErr) return uncErr;
108
+ try {
109
+ const existing = lstatSync(fullPath);
110
+ if (existing.isSymbolicLink && existing.isSymbolicLink()) {
111
+ return batch
112
+ ? `FAIL ${normalizeOutputPath(displayPath)}: symlink targets are not supported for write (would replace the link)`
113
+ : `Error: symlink targets are not supported for write (would replace the link): ${displayPath}`;
114
+ }
115
+ if (typeof isSpecialFileStat === 'function' && isSpecialFileStat(existing)) {
116
+ return batch
117
+ ? `FAIL ${normalizeOutputPath(displayPath)}: non-regular file (FIFO / device / socket) cannot be overwritten`
118
+ : `Error: non-regular file (FIFO / device / socket) cannot be overwritten: ${displayPath}`;
119
+ }
120
+ // Opt-in fast path: skip ONLY the read-before-write / stale-snapshot
121
+ // gate (codes 6/10/7). All other checks (symlink, special-file, UNC,
122
+ // stat-error surfacing above/below) still apply.
123
+ if (!allowUnreadOverwrite && existing.isFile() && !getReadSnapshot(fullPath, readStateScope)) {
124
+ return batch
125
+ ? `FAIL ${normalizeOutputPath(displayPath)}: file exists but has not been read yet — read before overwriting`
126
+ : `Error [code 6]: file exists but has not been read yet — read before overwriting: ${displayPath}`;
127
+ }
128
+ if (!allowUnreadOverwrite && existing.isFile()) {
129
+ const snapshot = getReadSnapshot(fullPath, readStateScope);
130
+ if (snapshot && !snapshotAllowsWholeFileOverwrite(snapshot)) {
131
+ const detail = snapshot.grepOnly === true
132
+ ? 'grep snapshot lacks full-file proof — read it in full before overwriting'
133
+ : 'partial-read snapshot — read it in full before overwriting';
134
+ return batch
135
+ ? `FAIL ${normalizeOutputPath(displayPath)}: ${detail}`
136
+ : `Error [code 10]: ${detail}: ${displayPath}`;
137
+ }
138
+ if (snapshot && isSnapshotStale(existing, snapshot, fullPath)) {
139
+ const hashOk = readContentIfSnapshotHashMatches(fullPath, snapshot, null, existing);
140
+ if (hashOk === null) {
141
+ return batch
142
+ ? `FAIL ${normalizeOutputPath(displayPath)}: file modified since read — read it again before overwriting`
143
+ : `Error [code 7]: file modified since read — read it again before overwriting: ${displayPath}`;
144
+ }
145
+ }
146
+ }
147
+ } catch (err) {
148
+ // Only ENOENT is "safe to create"; any other stat error (EACCES,
149
+ // EPERM, ELOOP, ENOTDIR, …) must surface so we don't silently
150
+ // overwrite or pretend the file is absent.
151
+ if (err && err.code === 'ENOENT') return null;
152
+ const reason = normalizeErrorMessage(err instanceof Error ? err.message : String(err));
153
+ return batch
154
+ ? `FAIL ${normalizeOutputPath(displayPath)}: stat failed before overwrite: ${reason}`
155
+ : `Error: stat failed before overwrite: ${reason}: ${displayPath}`;
156
+ }
157
+ return null;
158
+ }
159
+
160
+ async function writeOneUnlocked({ filePath, content, fullPath, readStateScope, sessionId, targetSnapshot }) {
161
+ mkdirSync(dirname(fullPath), { recursive: true });
162
+ // Preserve the existing file's encoding (BOM invariant); new files -> utf-8.
163
+ const writeContent = toWriteBuffer(content, detectExistingEncoding(fullPath));
164
+ const byteLength = Buffer.isBuffer(writeContent)
165
+ ? writeContent.length
166
+ : Buffer.byteLength(String(writeContent ?? ''), 'utf-8');
167
+ const oversized = byteLength > STREAMING_THRESHOLD_BYTES;
168
+ await atomicWrite(fullPath, writeContent, {
169
+ sessionId,
170
+ expectedTargetSnapshot: targetSnapshot,
171
+ });
172
+ let writtenStat = null;
173
+ try { writtenStat = statSync(fullPath); } catch {}
174
+ recordReadSnapshot(fullPath, writtenStat || undefined, readStateScope, {
175
+ source: 'write',
176
+ contentHash: hashText(writeContent),
177
+ });
178
+ return {
179
+ filePath,
180
+ fullPath,
181
+ content: writeContent,
182
+ stat: writtenStat,
183
+ oversized,
184
+ };
185
+ }
186
+
187
+ export async function executeWriteTool(args, workDir, readStateScope, options = {}) {
188
+ if (typeof args.file_path === 'string' && !args.path) args.path = args.file_path;
189
+ const allowUnreadOverwrite = args.allow_unread_overwrite === true;
190
+ if (Array.isArray(args.writes) && args.writes.length > 0) {
191
+ const items = args.writes.map((entry) => ({
192
+ path: normalizeInputPath(entry?.path ?? entry?.file_path),
193
+ content: entry?.content,
194
+ }));
195
+ const missing = items.filter((entry) => !entry.path || entry.content === undefined);
196
+ if (missing.length > 0) {
197
+ return 'Error: each write entry requires path and content';
198
+ }
199
+
200
+ // Reject duplicate targets (same lock key) and parent/child
201
+ // conflicts (one entry would write a file at a path another entry
202
+ // needs as a directory ancestor, or vice versa).
203
+ for (const entry of items) {
204
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(entry.path)) {
205
+ return `Error: cannot write Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(entry.path)}`;
206
+ }
207
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(entry.path)) {
208
+ return `Error: cannot write Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(entry.path)}`;
209
+ }
210
+ }
211
+ const resolvedItems = items.map((entry) => {
212
+ const fp = resolveAgainstCwd(entry.path, workDir);
213
+ return { entry, fullPath: fp, lockKey: pathLockKey(fp) };
214
+ });
215
+ for (const r of resolvedItems) {
216
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(r.fullPath)) {
217
+ return `Error: cannot write Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(r.entry.path)}`;
218
+ }
219
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(r.fullPath)) {
220
+ return `Error: cannot write Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(r.entry.path)}`;
221
+ }
222
+ }
223
+ const seenKeys = new Map();
224
+ for (const r of resolvedItems) {
225
+ if (seenKeys.has(r.lockKey)) {
226
+ return `Error: duplicate target in batch: ${normalizeOutputPath(r.entry.path)} collides with ${normalizeOutputPath(seenKeys.get(r.lockKey))}`;
227
+ }
228
+ seenKeys.set(r.lockKey, r.entry.path);
229
+ }
230
+ const sortedKeys = [...seenKeys.keys()].sort();
231
+ for (let i = 0; i + 1 < sortedKeys.length; i += 1) {
232
+ const a = sortedKeys[i];
233
+ const b = sortedKeys[i + 1];
234
+ if (b.length > a.length && b.startsWith(a) && (b[a.length] === '/' || b[a.length] === '\\')) {
235
+ return `Error: parent/child path conflict in batch: ${normalizeOutputPath(seenKeys.get(a))} would be a file but ${normalizeOutputPath(seenKeys.get(b))} requires it as a directory`;
236
+ }
237
+ }
238
+
239
+ // ATOMIC BATCH CONTRACT: UNC + preflight before any lock acquire
240
+ // (advisory lock mkdir must not run for rejected batches), then
241
+ // acquire all locks in sorted order, commit with per-file snapshots.
242
+ const allFullPaths = resolvedItems.map((r) => r.fullPath);
243
+ for (const { entry, fullPath } of resolvedItems) {
244
+ const uncErr = writeUncRejectMessage(entry.path, [entry.path, fullPath], { batch: true });
245
+ if (uncErr) {
246
+ return `Error: batch write rejected — UNC path blocked before lock; no files written\n${uncErr}`;
247
+ }
248
+ }
249
+ try {
250
+ await assertPathsReachable(allFullPaths);
251
+ } catch (err) {
252
+ const reason = normalizeErrorMessage(err instanceof Error ? err.message : String(err));
253
+ return `Error: batch write rejected — path reachability preflight failed; no files written\n${reason}`;
254
+ }
255
+ const preflightLines = [];
256
+ let preflightFailed = false;
257
+ for (const { entry, fullPath } of resolvedItems) {
258
+ const filePath = entry.path;
259
+ if (typeof entry.content === 'string') {
260
+ const _nulIdx = entry.content.indexOf('\u0000');
261
+ if (_nulIdx !== -1) {
262
+ preflightLines.push(`FAIL ${normalizeOutputPath(filePath)}: Error [code 11]: content contains NUL byte (U+0000) at offset ${_nulIdx} — source text must not contain NUL`);
263
+ preflightFailed = true;
264
+ continue;
265
+ }
266
+ }
267
+ const preflightError = validateOverwriteAllowed(fullPath, filePath, readStateScope, { batch: true, allowUnreadOverwrite });
268
+ if (preflightError) {
269
+ preflightLines.push(preflightError);
270
+ preflightFailed = true;
271
+ }
272
+ }
273
+ if (preflightFailed) {
274
+ return `Error: batch write rejected — preflight failed (${preflightLines.length} of ${resolvedItems.length}); no files written\n${preflightLines.join('\n')}`;
275
+ }
276
+
277
+ return withBuiltinPathLocks(allFullPaths, () =>
278
+ withAdvisoryLocks(allFullPaths, async () => {
279
+ // Commit phase: preflight already passed. Per-file failure
280
+ // here is still possible (mid-flight disk error, TOCTOU
281
+ // detection), but is uncommon and reported per entry.
282
+ const results = [];
283
+ const dirtyPaths = [];
284
+ const successfulWrites = [];
285
+ let commitFailed = 0;
286
+ for (const { entry, fullPath } of resolvedItems) {
287
+ const filePath = entry.path;
288
+ const content = entry.content;
289
+ try {
290
+ const targetSnapshot = captureTargetSnapshot(fullPath);
291
+ const written = await writeOneUnlocked({
292
+ filePath,
293
+ content,
294
+ fullPath,
295
+ readStateScope,
296
+ sessionId: options?.sessionId,
297
+ targetSnapshot,
298
+ });
299
+ successfulWrites.push(written);
300
+ dirtyPaths.push(fullPath);
301
+ results.push(`OK ${normalizeOutputPath(filePath)}`);
302
+ } catch (err) {
303
+ commitFailed += 1;
304
+ results.push(`FAIL ${normalizeOutputPath(filePath)}: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`);
305
+ }
306
+ }
307
+
308
+ if (dirtyPaths.length > 0) {
309
+ invalidateBuiltinResultCache(dirtyPaths);
310
+ for (const seed of successfulWrites) {
311
+ // Skip raw-content cache seeding for oversized
312
+ // writes — keeping a multi-megabyte payload in
313
+ // memory after rename defeats the streaming win.
314
+ if (seed.oversized) continue;
315
+ seedRawContentCacheAfterWrite(seed.fullPath, seed.content, seed.stat);
316
+ }
317
+ markCodeGraphDirtyPaths(dirtyPaths);
318
+ }
319
+ if (commitFailed > 0) {
320
+ return `Error: batch write failed during commit (${commitFailed} of ${resolvedItems.length}); preflight passed but some renames failed\n${results.join('\n')}`;
321
+ }
322
+ // ② completion progress (claude "Found N" parity). Best-effort,
323
+ // no-op when onProgress is absent (no progressToken).
324
+ if (typeof options?.onProgress === 'function') {
325
+ try { options.onProgress(`wrote ${successfulWrites.length} files`); } catch { /* best-effort */ }
326
+ }
327
+ return results.join('\n');
328
+ })
329
+ );
330
+ }
331
+
332
+ args.path = normalizeInputPath(args.path);
333
+ const filePath = args.path;
334
+ const content = args.content;
335
+ if (!filePath) return 'Error: path is required';
336
+ if (content === undefined) return 'Error: content is required';
337
+ if (typeof content === 'string') {
338
+ const _nulIdx = content.indexOf('\u0000');
339
+ if (_nulIdx !== -1) return `Error [code 11]: content contains NUL byte (U+0000) at offset ${_nulIdx} — source text must not contain NUL: ${filePath}`;
340
+ }
341
+ // R12: Win32 component guard — reject trailing dot/space or NTFS ADS
342
+ // suffix (foo.txt:ads) and reserved device names (NUL, CON, …) before
343
+ // resolve so a relative path can't be coerced into a device alias.
344
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(filePath)) {
345
+ return `Error: cannot write Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(filePath)}`;
346
+ }
347
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(filePath)) {
348
+ return `Error: cannot write Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(filePath)}`;
349
+ }
350
+
351
+ const fullPath = resolveAgainstCwd(filePath, workDir);
352
+ const uncErr = writeUncRejectMessage(filePath, [filePath, fullPath], { batch: false });
353
+ if (uncErr) return uncErr;
354
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(fullPath)) {
355
+ return `Error: cannot write Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(filePath)}`;
356
+ }
357
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(fullPath)) {
358
+ return `Error: cannot write Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(filePath)}`;
359
+ }
360
+ try {
361
+ await assertPathReachable(fullPath);
362
+ } catch (err) {
363
+ return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
364
+ }
365
+ const preflightError = validateOverwriteAllowed(fullPath, filePath, readStateScope, { allowUnreadOverwrite });
366
+ if (preflightError) return preflightError;
367
+ return withPathLock(fullPath, () =>
368
+ withAdvisoryLocks([fullPath], async () => {
369
+ let targetSnapshot;
370
+ try {
371
+ targetSnapshot = captureTargetSnapshot(fullPath);
372
+ } catch (err) {
373
+ return `Error: snapshot capture failed: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}: ${filePath}`;
374
+ }
375
+ try {
376
+ const written = await writeOneUnlocked({
377
+ filePath,
378
+ content,
379
+ fullPath,
380
+ readStateScope,
381
+ sessionId: options?.sessionId,
382
+ targetSnapshot,
383
+ });
384
+ invalidateBuiltinResultCache([fullPath]);
385
+ if (!written.oversized) {
386
+ seedRawContentCacheAfterWrite(fullPath, written.content, written.stat);
387
+ }
388
+ markCodeGraphDirtyPaths([fullPath]);
389
+ // ② completion progress (claude "Found N" parity). Best-effort,
390
+ // no-op when onProgress is absent (no progressToken).
391
+ if (typeof options?.onProgress === 'function') {
392
+ try { options.onProgress(`wrote ${normalizeOutputPath(filePath)}`); } catch { /* best-effort */ }
393
+ }
394
+ return `Written: ${normalizeOutputPath(filePath)}`;
395
+ }
396
+ catch (err) {
397
+ return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
398
+ }
399
+ })
400
+ );
401
+ }