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,540 @@
1
+ // Cycle 3 — user-curated core memory review.
2
+ //
3
+ // Walks every row in core_entries (via listCore('*')), retrieves the related
4
+ // current memory for each row using searchRelevantHybrid, packs both into a
5
+ // {{CORE_REVIEW}} block for defaults/cycle3-review-prompt.md, then asks the
6
+ // maintenance-preset LLM for one verdict per id. By default Cycle3 performs
7
+ // conservative cleanup: safe compression updates and strict duplicate merges
8
+ // are applied, while deletes stay proposals unless explicitly confirmed.
9
+ //
10
+ // Verdict line grammar (mirrors parseUnifiedFormat in memory-cycle2.mjs):
11
+ // <id>|keep
12
+ // <id>|update|<element>|<summary>
13
+ // <id>|merge|<target_id>|<source_ids_csv>
14
+ // <id>|delete
15
+
16
+ import { existsSync, readFileSync } from 'fs'
17
+ import { join } from 'path'
18
+ import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
19
+ import { callBridgeLlm } from './agent-ipc.mjs'
20
+ import { listCore, editCore, deleteCore, CORE_SUMMARY_MAX } from './core-memory-store.mjs'
21
+ import { loadCurrentRulesDigest } from './memory-cycle2.mjs'
22
+ import { embedText } from './embedding-provider.mjs'
23
+ import { searchRelevantHybrid } from './memory-recall-store.mjs'
24
+
25
+ function resourceDir() {
26
+ if (process.env.CLAUDE_PLUGIN_ROOT) return process.env.CLAUDE_PLUGIN_ROOT
27
+ throw new Error('CLAUDE_PLUGIN_ROOT env var required for prompt loading')
28
+ }
29
+
30
+ async function invokeLlm(prompt, mode, preset, timeout, llmCall = callBridgeLlm) {
31
+ return await llmCall({
32
+ role: 'cycle3-agent',
33
+ taskType: 'maintenance',
34
+ mode,
35
+ preset,
36
+ timeout,
37
+ cwd: null,
38
+ }, prompt)
39
+ }
40
+
41
+ function throwIfAborted(signal) {
42
+ if (signal?.aborted) throw signal.reason ?? new Error('aborted')
43
+ }
44
+
45
+ function resolveApplyMode(config, options = {}) {
46
+ if (options?.apply === true) return 'confirmed'
47
+ if (options?.apply === false) return 'proposal'
48
+ const raw = String(options?.applyMode || config?.cycle3?.applyMode || 'conservative').trim().toLowerCase()
49
+ if (raw === 'proposal' || raw === 'dry-run' || raw === 'dryrun') return 'proposal'
50
+ return 'conservative'
51
+ }
52
+
53
+ function normalizeComparable(value) {
54
+ return String(value ?? '')
55
+ .toLowerCase()
56
+ .replace(/[|`"'“”‘’()[\]{}<>]/g, ' ')
57
+ .replace(/\s+/g, ' ')
58
+ .trim()
59
+ }
60
+
61
+ function compactComparable(value) {
62
+ return normalizeComparable(value).replace(/\s+/g, '')
63
+ }
64
+
65
+ function charDice(a, b) {
66
+ const aa = compactComparable(a)
67
+ const bb = compactComparable(b)
68
+ if (!aa || !bb) return 0
69
+ if (aa === bb) return 1
70
+ if (aa.length < 3 || bb.length < 3) return aa === bb ? 1 : 0
71
+ const grams = (s) => {
72
+ const m = new Map()
73
+ for (let i = 0; i <= s.length - 3; i++) {
74
+ const g = s.slice(i, i + 3)
75
+ m.set(g, (m.get(g) || 0) + 1)
76
+ }
77
+ return m
78
+ }
79
+ const ga = grams(aa)
80
+ const gb = grams(bb)
81
+ let overlap = 0
82
+ for (const [g, n] of ga) overlap += Math.min(n, gb.get(g) || 0)
83
+ const total = [...ga.values()].reduce((s, n) => s + n, 0) + [...gb.values()].reduce((s, n) => s + n, 0)
84
+ return total > 0 ? (2 * overlap) / total : 0
85
+ }
86
+
87
+ function coreText(core) {
88
+ return `${core?.element || ''}\n${core?.summary || ''}`
89
+ }
90
+
91
+ function isSafeConservativeUpdate(current, action) {
92
+ if (!current || !action?.element || !action?.summary) return { ok: false, reason: 'missing text' }
93
+ const newElement = normalizeComparable(action.element)
94
+ const newSummary = normalizeComparable(action.summary)
95
+ if (!newElement || !newSummary) return { ok: false, reason: 'empty rewrite' }
96
+ if (newSummary.length > CORE_SUMMARY_MAX) return { ok: false, reason: 'summary too long' }
97
+
98
+ const oldText = coreText(current)
99
+ const newText = `${action.element}\n${action.summary}`
100
+ const oldLen = normalizeComparable(oldText).length
101
+ const newLen = normalizeComparable(newText).length
102
+ if (oldLen > 0 && newLen > oldLen + 20) return { ok: false, reason: 'rewrite expands entry' }
103
+
104
+ const sim = charDice(oldText, newText)
105
+ if (sim < 0.28) return { ok: false, reason: `rewrite drift sim=${sim.toFixed(2)}` }
106
+ return { ok: true, reason: 'safe compression' }
107
+ }
108
+
109
+ function findElementConflict(coreById, currentId, element, projectId) {
110
+ const nextElement = String(element ?? '').trim()
111
+ if (!nextElement) return null
112
+ for (const [id, row] of coreById) {
113
+ if (Number(id) === Number(currentId)) continue
114
+ if ((row.project_id ?? null) !== (projectId ?? null)) continue
115
+ if (String(row.element ?? '') === nextElement) return Number(id)
116
+ }
117
+ return null
118
+ }
119
+
120
+ function isStrictDuplicate(a, b) {
121
+ if (!a || !b) return false
122
+ const ae = compactComparable(a.element)
123
+ const be = compactComparable(b.element)
124
+ const as = compactComparable(a.summary)
125
+ const bs = compactComparable(b.summary)
126
+ if (as && bs && as === bs) return true
127
+ if (ae && be && ae === be && charDice(a.summary, b.summary) >= 0.65) return true
128
+ const sim = charDice(coreText(a), coreText(b))
129
+ return sim >= 0.78
130
+ }
131
+
132
+ function formatRelatedRow(r) {
133
+ const tag = r.project_id ? r.project_id : 'COMMON'
134
+ const stat = r.status ? `[${r.status}]` : '[?]'
135
+ const el = r.element ? `el:${r.element} ` : ''
136
+ const sm = String(r.summary || r.content || '').replace(/\s+/g, ' ').slice(0, 160)
137
+ return ` - id:${r.id} ${stat} ${tag} ${r.category ?? '?'} ${el}sm:${sm}`
138
+ }
139
+
140
+ function formatCoreBlock(core, related) {
141
+ const tag = core.project_id ? core.project_id : 'COMMON'
142
+ const head = `## CORE id:${core.id} ${tag} ${core.category}`
143
+ const el = ` element: ${core.element}`
144
+ const sm = ` summary: ${String(core.summary || '').replace(/\s+/g, ' ')}`
145
+ const rel = related && related.length
146
+ ? ` related current memory (top ${related.length}):\n` + related.map(formatRelatedRow).join('\n')
147
+ : ` related current memory: (none found)`
148
+ return [head, el, sm, rel].join('\n')
149
+ }
150
+
151
+ // Parse cycle3 verdict lines. Returns { actions } where each action is one of
152
+ // { id, verb:'keep' } | { id, verb:'update', element, summary }
153
+ // | { id, verb:'merge', targetId, sourceIds:[...] } | { id, verb:'delete' }.
154
+ function parseVerdicts(raw, idSet) {
155
+ if (raw == null) return null
156
+ const text = String(raw).trim()
157
+ if (!text) return { actions: [] }
158
+ const lines = text.split('\n')
159
+ const actions = []
160
+ let sawValid = false
161
+ for (const rawLine of lines) {
162
+ const line = rawLine.trim().replace(/^\d+[\.)]?\s+(?=\d+\|)/, '')
163
+ if (!line) continue
164
+ if (line.startsWith('//') || line.startsWith('#')) continue
165
+ if (line.startsWith('```')) continue
166
+ const parts = line.split('|')
167
+ if (parts.length < 2) continue
168
+ const id = Number(parts[0].trim())
169
+ const verb = parts[1].trim().toLowerCase()
170
+ if (!Number.isFinite(id) || !verb) continue
171
+ if (!idSet.has(id)) continue
172
+ sawValid = true
173
+ if (verb === 'keep') {
174
+ actions.push({ id, verb: 'keep' })
175
+ } else if (verb === 'update') {
176
+ const element = (parts[2] ?? '').trim()
177
+ const summary = parts.slice(3).join('|').trim()
178
+ if (!element || !summary) continue
179
+ actions.push({ id, verb: 'update', element, summary })
180
+ } else if (verb === 'merge') {
181
+ const targetId = Number((parts[2] ?? '').trim())
182
+ const sourceIds = [...new Set((parts[3] ?? '')
183
+ .split(',')
184
+ .map(s => Number(String(s).trim()))
185
+ .filter(n => Number.isFinite(n) && idSet.has(n)))]
186
+ if (!Number.isFinite(targetId) || !idSet.has(targetId)) {
187
+ process.stderr.write(`[cycle3] merge rejected: id=${id} invalid target\n`)
188
+ continue
189
+ }
190
+ if (sourceIds.length === 0) {
191
+ process.stderr.write(`[cycle3] merge rejected: id=${id} invalid sources\n`)
192
+ continue
193
+ }
194
+ if (targetId !== id && !sourceIds.includes(id)) {
195
+ process.stderr.write(
196
+ `[cycle3] merge rejected: id=${id} must be target or listed source (target=${targetId} sources=${sourceIds.join(',')})\n`,
197
+ )
198
+ continue
199
+ }
200
+ actions.push({ id, verb: 'merge', targetId, sourceIds })
201
+ } else if (verb === 'delete') {
202
+ actions.push({ id, verb: 'delete' })
203
+ }
204
+ }
205
+ if (!sawValid) return null
206
+ return { actions }
207
+ }
208
+
209
+ const _runCycle3InFlight = new WeakMap()
210
+
211
+ export async function runCycle3(db, config, dataDir, options = {}) {
212
+ const signal = options?.signal
213
+ throwIfAborted(signal)
214
+ const applyMode = resolveApplyMode(config, options)
215
+ const partial = {
216
+ reviewed: 0, kept: 0, updated: 0, merged: 0, deleted: 0,
217
+ proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
218
+ held: { updated: 0, merged: 0, deleted: 0 },
219
+ applied: applyMode !== 'proposal',
220
+ applyMode,
221
+ details: [],
222
+ }
223
+ if (_runCycle3InFlight.has(db)) {
224
+ process.stderr.write('[cycle3] skipped: already in flight for this db\n')
225
+ return { ...partial, skippedInFlight: true }
226
+ }
227
+ const client = await db._pool.connect()
228
+ let gotLock = false
229
+ try {
230
+ const r = await client.query(`SELECT pg_try_advisory_lock(hashtext($1)) AS got`, ['mixdog.cycle3'])
231
+ gotLock = r.rows[0]?.got === true
232
+ } catch (err) {
233
+ client.release()
234
+ if (signal?.aborted) throw signal.reason ?? err
235
+ process.stderr.write(`[cycle3] advisory lock query failed: ${err.message}\n`)
236
+ return { ...partial, skippedInFlight: true }
237
+ }
238
+ if (!gotLock) {
239
+ client.release()
240
+ process.stderr.write('[cycle3] skipped: advisory lock held by another worker\n')
241
+ return { ...partial, skippedInFlight: true }
242
+ }
243
+ const promise = (async () => {
244
+ try {
245
+ return await _runCycle3Impl(db, config, dataDir, options)
246
+ } finally {
247
+ try { await client.query(`SELECT pg_advisory_unlock(hashtext($1))`, ['mixdog.cycle3']) } catch {}
248
+ client.release()
249
+ }
250
+ })()
251
+ _runCycle3InFlight.set(db, promise)
252
+ try { return await promise }
253
+ finally { _runCycle3InFlight.delete(db) }
254
+ }
255
+
256
+ async function _runCycle3Impl(db, config, dataDir, options = {}) {
257
+ const signal = options?.signal
258
+ const applyMode = resolveApplyMode(config, options)
259
+ const confirmed = applyMode === 'confirmed'
260
+ const conservative = applyMode === 'conservative'
261
+ const mutate = confirmed || conservative
262
+ throwIfAborted(signal)
263
+ if (!dataDir) throw new Error('runCycle3: dataDir required')
264
+
265
+ const cores = await listCore(dataDir, '*')
266
+ throwIfAborted(signal)
267
+ if (!cores || cores.length === 0) {
268
+ process.stderr.write(`[cycle3] reviewed=0 kept=0 updated=0 merged=0 deleted=0 mode=${applyMode} (no core_entries)\n`)
269
+ return {
270
+ reviewed: 0, kept: 0, updated: 0, merged: 0, deleted: 0,
271
+ proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
272
+ held: { updated: 0, merged: 0, deleted: 0 },
273
+ applied: mutate,
274
+ applyMode,
275
+ details: [],
276
+ }
277
+ }
278
+
279
+ // Per-core related-memory recall.
280
+ const blocks = []
281
+ for (const core of cores) {
282
+ throwIfAborted(signal)
283
+ const queryText = `${core.element}\n${String(core.summary || '')}`.trim()
284
+ let related = []
285
+ try {
286
+ const scope = core.project_id ? String(core.project_id) : 'common'
287
+ let queryVector = null
288
+ try {
289
+ queryVector = await embedText(queryText)
290
+ } catch (err) {
291
+ if (signal?.aborted) throw signal.reason ?? err
292
+ process.stderr.write(`[cycle3] embedding failed for core id=${core.id}: ${err.message}\n`)
293
+ }
294
+ related = await searchRelevantHybrid(db, queryText, {
295
+ limit: 8,
296
+ projectScope: scope,
297
+ includeMembers: false,
298
+ queryVector: Array.isArray(queryVector) ? queryVector : undefined,
299
+ })
300
+ } catch (err) {
301
+ if (signal?.aborted) throw signal.reason ?? err
302
+ process.stderr.write(`[cycle3] recall failed for core id=${core.id}: ${err.message}\n`)
303
+ related = []
304
+ }
305
+ throwIfAborted(signal)
306
+ blocks.push(formatCoreBlock(core, related))
307
+ }
308
+ const coreReview = blocks.join('\n\n')
309
+
310
+ // Load + fill prompt template.
311
+ const promptPath = join(resourceDir(), 'defaults', 'cycle3-review-prompt.md')
312
+ if (!existsSync(promptPath)) {
313
+ throw new Error(`runCycle3: prompt file missing at ${promptPath}`)
314
+ }
315
+ const template = readFileSync(promptPath, 'utf8')
316
+ const rulesDigest = loadCurrentRulesDigest() || '(no current rules digest available)'
317
+ const prompt = template
318
+ .replace('{{CORE_REVIEW}}', coreReview)
319
+ .replace('{{CURRENT_RULES}}', rulesDigest)
320
+
321
+ const preset = resolveMaintenancePreset('cycle3')
322
+ const timeout = Number(config?.cycle3?.timeout ?? 600000)
323
+ const mode = 'cycle3-review'
324
+
325
+ process.stderr.write(`[cycle3-diag] prompt=${prompt.length} bytes; cores=${cores.length}\n`)
326
+
327
+ let raw
328
+ try {
329
+ throwIfAborted(signal)
330
+ raw = await invokeLlm(prompt, mode, preset, timeout, options.callLlm)
331
+ } catch (err) {
332
+ if (signal?.aborted) throw signal.reason ?? err
333
+ process.stderr.write(`[cycle3] LLM error: ${err.message}\n`)
334
+ return {
335
+ reviewed: cores.length, kept: 0, updated: 0, merged: 0, deleted: 0,
336
+ proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
337
+ held: { updated: 0, merged: 0, deleted: 0 },
338
+ applied: mutate,
339
+ applyMode,
340
+ details: [], error: err.message,
341
+ }
342
+ }
343
+ throwIfAborted(signal)
344
+
345
+ const idSet = new Set(cores.map(c => Number(c.id)))
346
+ const coreById = new Map(cores.map(c => [Number(c.id), c]))
347
+ const parsed = parseVerdicts(raw, idSet)
348
+ if (!parsed) {
349
+ process.stderr.write(
350
+ `[cycle3] unparseable response — skipping (${String(raw ?? '').replace(/\s+/g, ' ').slice(0, 200)})\n`,
351
+ )
352
+ return {
353
+ reviewed: cores.length, kept: 0, updated: 0, merged: 0, deleted: 0,
354
+ proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
355
+ held: { updated: 0, merged: 0, deleted: 0 },
356
+ applied: mutate,
357
+ applyMode,
358
+ details: [], error: 'unparseable',
359
+ }
360
+ }
361
+ const seenVerdictIds = new Set()
362
+ const dedupedActions = []
363
+ for (const action of parsed.actions) {
364
+ const vid = Number(action.id)
365
+ if (seenVerdictIds.has(vid)) {
366
+ process.stderr.write(`[cycle3] duplicate verdict rejected: id=${vid} verb=${action.verb}\n`)
367
+ continue
368
+ }
369
+ seenVerdictIds.add(vid)
370
+ dedupedActions.push(action)
371
+ }
372
+ parsed.actions = dedupedActions
373
+ const actionIds = new Set(parsed.actions.map(a => Number(a.id)).filter(n => Number.isFinite(n)))
374
+ for (const id of idSet) {
375
+ if (!actionIds.has(id)) parsed.actions.push({ id, verb: 'keep' })
376
+ }
377
+
378
+ let kept = 0, updated = 0, merged = 0, deleted = 0
379
+ const proposed = { kept: 0, updated: 0, merged: 0, deleted: 0 }
380
+ const held = { updated: 0, merged: 0, deleted: 0 }
381
+ const details = []
382
+ const touched = new Set() // ids already acted on this cycle — avoid double action
383
+
384
+ // Core-store edit/delete calls are the mutation unit; checkpoints sit before
385
+ // each action/source and after each awaited unit, not inside one file-store write.
386
+ for (const a of parsed.actions) {
387
+ throwIfAborted(signal)
388
+ if (touched.has(a.id)) continue
389
+ if (a.verb === 'keep') {
390
+ kept++
391
+ proposed.kept++
392
+ details.push({ id: a.id, verb: 'keep' })
393
+ touched.add(a.id)
394
+ continue
395
+ }
396
+ if (a.verb === 'update') {
397
+ proposed.updated++
398
+ const safety = conservative ? isSafeConservativeUpdate(coreById.get(a.id), a) : { ok: true, reason: 'confirmed' }
399
+ const conflictId = conservative
400
+ ? findElementConflict(coreById, a.id, a.element, coreById.get(a.id)?.project_id ?? null)
401
+ : null
402
+ if (!mutate || (conservative && (!safety.ok || conflictId != null))) {
403
+ held.updated++
404
+ details.push({
405
+ id: a.id, verb: 'update', element: a.element, summary: a.summary,
406
+ applied: false, held: true,
407
+ reason: !mutate ? 'proposal mode' : (conflictId != null ? `element conflicts with core id=${conflictId}` : safety.reason),
408
+ })
409
+ touched.add(a.id)
410
+ continue
411
+ }
412
+ try {
413
+ await editCore(dataDir, a.id, { element: a.element, summary: a.summary })
414
+ updated++
415
+ details.push({ id: a.id, verb: 'update', element: a.element, summary: a.summary, applied: true })
416
+ touched.add(a.id)
417
+ } catch (err) {
418
+ if (signal?.aborted) throw signal.reason ?? err
419
+ process.stderr.write(`[cycle3] update failed id=${a.id}: ${err.message}\n`)
420
+ details.push({ id: a.id, verb: 'update', error: err.message })
421
+ }
422
+ continue
423
+ }
424
+ if (a.verb === 'delete') {
425
+ proposed.deleted++
426
+ if (!confirmed) {
427
+ held.deleted++
428
+ details.push({
429
+ id: a.id, verb: 'delete', applied: false, held: true,
430
+ reason: conservative ? 'delete requires APPLY CYCLE3' : 'proposal mode',
431
+ })
432
+ touched.add(a.id)
433
+ continue
434
+ }
435
+ try {
436
+ await deleteCore(dataDir, a.id)
437
+ deleted++
438
+ details.push({ id: a.id, verb: 'delete', applied: true })
439
+ touched.add(a.id)
440
+ } catch (err) {
441
+ if (signal?.aborted) throw signal.reason ?? err
442
+ process.stderr.write(`[cycle3] delete failed id=${a.id}: ${err.message}\n`)
443
+ details.push({ id: a.id, verb: 'delete', error: err.message })
444
+ }
445
+ continue
446
+ }
447
+ if (a.verb === 'merge') {
448
+ if (a.targetId !== a.id && !a.sourceIds.includes(a.id)) {
449
+ process.stderr.write(
450
+ `[cycle3] merge rejected: id=${a.id} must be target or listed source (target=${a.targetId} sources=${a.sourceIds.join(',')})\n`,
451
+ )
452
+ touched.add(a.id)
453
+ touched.add(a.targetId)
454
+ for (const sid of a.sourceIds) touched.add(sid)
455
+ details.push({ id: a.id, verb: 'merge', error: 'id must be target or listed source' })
456
+ continue
457
+ }
458
+ // Only merge within the same project pool. Survivor = targetId.
459
+ const target = coreById.get(a.targetId)
460
+ if (!target) {
461
+ details.push({ id: a.id, verb: 'merge', error: `target ${a.targetId} not found` })
462
+ touched.add(a.id)
463
+ continue
464
+ }
465
+ const validSources = []
466
+ for (const sid of a.sourceIds) {
467
+ throwIfAborted(signal)
468
+ if (sid === a.targetId) continue
469
+ if (touched.has(sid)) continue
470
+ const src = coreById.get(sid)
471
+ if (!src) continue
472
+ if ((src.project_id ?? null) !== (target.project_id ?? null)) {
473
+ process.stderr.write(`[cycle3] merge skipped src=${sid} target=${a.targetId} (project pool mismatch)\n`)
474
+ continue
475
+ }
476
+ validSources.push(sid)
477
+ }
478
+ if (validSources.length === 0) {
479
+ details.push({ id: a.id, verb: 'merge', error: 'no valid sources' })
480
+ continue
481
+ }
482
+ // Refresh target via editCore so summary/element reflect the merged form.
483
+ // The verdict carries no rewritten text → fall back to the target's
484
+ // existing element/summary unmodified; the LLM expressed merge intent
485
+ // alone. editCore requires a change, so when no text drift is supplied
486
+ // we skip the target update and just absorb sources.
487
+ proposed.merged++
488
+ const safeSources = conservative
489
+ ? validSources.filter(sid => isStrictDuplicate(target, coreById.get(sid)))
490
+ : validSources
491
+ const mergedDetail = {
492
+ id: a.id, verb: 'merge', targetId: a.targetId, sourceIds: validSources,
493
+ removed: [], applied: false, applyMode,
494
+ }
495
+ if (!mutate || safeSources.length === 0) {
496
+ held.merged++
497
+ mergedDetail.held = true
498
+ mergedDetail.reason = !mutate ? 'proposal mode' : 'no strict duplicate source'
499
+ details.push(mergedDetail)
500
+ touched.add(a.targetId)
501
+ validSources.forEach(sid => touched.add(sid))
502
+ continue
503
+ }
504
+ for (const sid of safeSources) {
505
+ throwIfAborted(signal)
506
+ try {
507
+ await deleteCore(dataDir, sid)
508
+ mergedDetail.removed.push(sid)
509
+ touched.add(sid)
510
+ } catch (err) {
511
+ if (signal?.aborted) throw signal.reason ?? err
512
+ process.stderr.write(`[cycle3] merge delete src=${sid} failed: ${err.message}\n`)
513
+ }
514
+ }
515
+ if (mergedDetail.removed.length > 0) {
516
+ merged++
517
+ mergedDetail.applied = true
518
+ if (safeSources.length < validSources.length) {
519
+ held.merged++
520
+ mergedDetail.heldSources = validSources.filter(sid => !safeSources.includes(sid))
521
+ }
522
+ touched.add(a.targetId)
523
+ }
524
+ details.push(mergedDetail)
525
+ continue
526
+ }
527
+ }
528
+
529
+ throwIfAborted(signal)
530
+
531
+ process.stderr.write(
532
+ `[cycle3] reviewed=${cores.length} kept=${kept}` +
533
+ ` proposed_update=${proposed.updated} proposed_merge=${proposed.merged} proposed_delete=${proposed.deleted}` +
534
+ ` applied_update=${updated} applied_merge=${merged} applied_delete=${deleted}` +
535
+ ` held_update=${held.updated} held_merge=${held.merged} held_delete=${held.deleted}` +
536
+ ` mode=${applyMode}\n`,
537
+ )
538
+
539
+ return { reviewed: cores.length, kept, updated, merged, deleted, proposed, held, applied: mutate, applyMode, details }
540
+ }