mixdog 0.7.18 → 0.8.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 (847) hide show
  1. package/README.md +37 -331
  2. package/package.json +67 -99
  3. package/scripts/boot-smoke.mjs +94 -0
  4. package/scripts/build-tui.mjs +52 -0
  5. package/scripts/compact-smoke.mjs +199 -0
  6. package/scripts/lead-workflow-smoke.mjs +598 -0
  7. package/scripts/live-worker-smoke.mjs +239 -0
  8. package/scripts/output-style-smoke.mjs +101 -0
  9. package/scripts/session-context-bench.mjs +172 -0
  10. package/scripts/smoke-loop-report.mjs +221 -0
  11. package/scripts/smoke-loop.mjs +201 -0
  12. package/scripts/smoke.mjs +113 -0
  13. package/scripts/tool-failures.mjs +143 -0
  14. package/scripts/tool-smoke.mjs +452 -0
  15. package/src/agents/debugger/AGENT.md +3 -0
  16. package/src/agents/debugger/agent.json +6 -0
  17. package/src/agents/explore/AGENT.md +4 -0
  18. package/src/agents/explore/agent.json +6 -0
  19. package/src/agents/heavy-worker/AGENT.md +3 -0
  20. package/src/agents/heavy-worker/agent.json +6 -0
  21. package/src/agents/maintainer/AGENT.md +3 -0
  22. package/src/agents/maintainer/agent.json +6 -0
  23. package/src/agents/reviewer/AGENT.md +3 -0
  24. package/src/agents/reviewer/agent.json +6 -0
  25. package/src/agents/scheduler-task.md +3 -0
  26. package/src/agents/web-researcher/AGENT.md +3 -0
  27. package/src/agents/web-researcher/agent.json +6 -0
  28. package/src/agents/webhook-handler.md +3 -0
  29. package/src/agents/worker/AGENT.md +3 -0
  30. package/src/agents/worker/agent.json +6 -0
  31. package/src/app.mjs +90 -0
  32. package/src/cli.mjs +11 -0
  33. package/src/defaults/hidden-roles.json +72 -0
  34. package/src/defaults/mixdog-config.template.json +15 -0
  35. package/src/hooks/lib/permission-evaluator.cjs +488 -0
  36. package/src/hooks/lib/settings-loader.cjs +112 -0
  37. package/src/lib/keychain-cjs.cjs +332 -0
  38. package/src/lib/plugin-paths.cjs +28 -0
  39. package/src/lib/rules-builder.cjs +315 -0
  40. package/src/mixdog-session-runtime.mjs +3813 -0
  41. package/src/output-styles/default.md +38 -0
  42. package/src/output-styles/extreme-simple.md +17 -0
  43. package/src/output-styles/simple.md +17 -0
  44. package/src/repl.mjs +330 -0
  45. package/src/rules/bridge/00-common.md +5 -0
  46. package/src/rules/bridge/20-skip-protocol.md +11 -0
  47. package/src/rules/bridge/30-explorer.md +4 -0
  48. package/src/rules/bridge/40-cycle1-agent.md +28 -0
  49. package/src/rules/bridge/41-cycle2-agent.md +59 -0
  50. package/src/rules/lead/00-tool-lead.md +5 -0
  51. package/src/rules/lead/01-general.md +5 -0
  52. package/src/rules/lead/02-channels.md +3 -0
  53. package/src/rules/lead/04-workflow.md +12 -0
  54. package/src/rules/shared/00-language.md +3 -0
  55. package/src/rules/shared/01-tool.md +3 -0
  56. package/src/runtime/agent/orchestrator/bridge-trace.mjs +814 -0
  57. package/src/runtime/agent/orchestrator/cache-mtime.mjs +60 -0
  58. package/src/runtime/agent/orchestrator/config.mjs +446 -0
  59. package/src/runtime/agent/orchestrator/context/collect.mjs +796 -0
  60. package/src/runtime/agent/orchestrator/dispatch-persist.mjs +417 -0
  61. package/src/runtime/agent/orchestrator/internal-roles.mjs +188 -0
  62. package/src/runtime/agent/orchestrator/internal-tools.mjs +88 -0
  63. package/src/runtime/agent/orchestrator/mcp/client.mjs +345 -0
  64. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2104 -0
  65. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +784 -0
  66. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +341 -0
  67. package/src/runtime/agent/orchestrator/providers/gemini.mjs +1679 -0
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +959 -0
  69. package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +213 -0
  70. package/src/runtime/agent/orchestrator/providers/model-cache.mjs +38 -0
  71. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +471 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +615 -0
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +808 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +1719 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +2587 -0
  76. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1953 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +136 -0
  78. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +317 -0
  79. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +109 -0
  80. package/src/runtime/agent/orchestrator/providers/registry.mjs +247 -0
  81. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +332 -0
  82. package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +11 -0
  83. package/src/runtime/agent/orchestrator/providers/trace-utils.mjs +50 -0
  84. package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  85. package/src/runtime/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  86. package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +318 -0
  87. package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +367 -0
  88. package/src/runtime/agent/orchestrator/session/compact.mjs +882 -0
  89. package/src/runtime/agent/orchestrator/session/context-utils.mjs +233 -0
  90. package/src/runtime/agent/orchestrator/session/loop.mjs +2269 -0
  91. package/src/runtime/agent/orchestrator/session/manager.mjs +2972 -0
  92. package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
  93. package/src/runtime/agent/orchestrator/session/store.mjs +870 -0
  94. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  95. package/src/runtime/agent/orchestrator/smart-bridge/bridge-llm.mjs +339 -0
  96. package/src/runtime/agent/orchestrator/smart-bridge/cache-strategy.mjs +419 -0
  97. package/src/runtime/agent/orchestrator/stall-policy.mjs +227 -0
  98. package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +235 -0
  99. package/src/runtime/agent/orchestrator/tools/bash-session.mjs +723 -0
  100. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  101. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +389 -0
  102. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +637 -0
  103. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +165 -0
  104. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +104 -0
  105. package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +194 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +596 -0
  107. package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +110 -0
  108. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +153 -0
  109. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  110. package/src/runtime/agent/orchestrator/tools/builtin/read-open.mjs +189 -0
  111. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +731 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +168 -0
  113. package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +465 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +160 -0
  116. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +982 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +1087 -0
  118. package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +231 -0
  119. package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +223 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin.mjs +478 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  122. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4102 -0
  123. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  124. package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +154 -0
  125. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +26 -0
  126. package/src/runtime/agent/orchestrator/tools/patch-binary-fetcher.mjs +143 -0
  127. package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +18 -0
  128. package/src/runtime/agent/orchestrator/tools/patch.mjs +2772 -0
  129. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +114 -0
  130. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +880 -0
  131. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +312 -0
  132. package/src/runtime/channels/backends/discord.mjs +781 -0
  133. package/src/runtime/channels/data/voice-runtime-manifest.json +138 -0
  134. package/src/runtime/channels/index.mjs +3309 -0
  135. package/src/runtime/channels/lib/config.mjs +285 -0
  136. package/src/runtime/channels/lib/drop-trace.mjs +71 -0
  137. package/src/runtime/channels/lib/event-pipeline.mjs +81 -0
  138. package/src/runtime/channels/lib/holidays.mjs +138 -0
  139. package/src/runtime/channels/lib/hook-pipe-server.mjs +671 -0
  140. package/src/runtime/channels/lib/output-forwarder.mjs +765 -0
  141. package/src/runtime/channels/lib/runtime-paths.mjs +497 -0
  142. package/src/runtime/channels/lib/scheduler.mjs +710 -0
  143. package/src/runtime/channels/lib/session-discovery.mjs +102 -0
  144. package/src/runtime/channels/lib/state-file.mjs +68 -0
  145. package/src/runtime/channels/lib/status-snapshot.mjs +224 -0
  146. package/src/runtime/channels/lib/tool-format.mjs +122 -0
  147. package/src/runtime/channels/lib/transcript-discovery.mjs +195 -0
  148. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +734 -0
  149. package/src/runtime/channels/lib/webhook.mjs +1288 -0
  150. package/src/runtime/channels/tool-defs.mjs +177 -0
  151. package/src/runtime/lib/keychain-cjs.cjs +289 -0
  152. package/src/runtime/memory/data/runtime-manifest.json +40 -0
  153. package/src/runtime/memory/index.mjs +3706 -0
  154. package/src/runtime/memory/lib/core-memory-store.mjs +336 -0
  155. package/src/runtime/memory/lib/embedding-provider.mjs +275 -0
  156. package/src/runtime/memory/lib/embedding-worker.mjs +331 -0
  157. package/src/runtime/memory/lib/memory-cycle-requests.mjs +276 -0
  158. package/src/runtime/memory/lib/memory-cycle1.mjs +783 -0
  159. package/src/runtime/memory/lib/memory-cycle2.mjs +1389 -0
  160. package/src/runtime/memory/lib/memory-cycle3.mjs +646 -0
  161. package/src/runtime/memory/lib/memory-embed.mjs +300 -0
  162. package/src/runtime/memory/lib/memory-ops-policy.mjs +149 -0
  163. package/src/runtime/memory/lib/memory-recall-store.mjs +644 -0
  164. package/src/runtime/memory/lib/memory.mjs +418 -0
  165. package/src/runtime/memory/lib/pg/adapter.mjs +328 -0
  166. package/src/runtime/memory/lib/pg/process.mjs +366 -0
  167. package/src/runtime/memory/lib/pg/supervisor.mjs +495 -0
  168. package/src/runtime/memory/lib/runtime-fetcher.mjs +464 -0
  169. package/src/runtime/memory/lib/trace-store.mjs +734 -0
  170. package/src/runtime/memory/tool-defs.mjs +79 -0
  171. package/src/runtime/search/index.mjs +925 -0
  172. package/src/runtime/search/lib/config.mjs +61 -0
  173. package/src/runtime/search/lib/web-tools.mjs +1278 -0
  174. package/src/runtime/search/tool-defs.mjs +64 -0
  175. package/src/runtime/shared/atomic-file.mjs +435 -0
  176. package/src/runtime/shared/background-tasks.mjs +376 -0
  177. package/src/runtime/shared/child-guardian.mjs +98 -0
  178. package/src/runtime/shared/config.mjs +393 -0
  179. package/src/runtime/shared/err-text.mjs +121 -0
  180. package/src/runtime/shared/launcher-control.mjs +259 -0
  181. package/src/runtime/shared/llm/http-agent.mjs +129 -0
  182. package/src/runtime/shared/open-url.mjs +37 -0
  183. package/src/runtime/shared/plugin-paths.mjs +25 -0
  184. package/src/runtime/shared/process-shutdown.mjs +147 -0
  185. package/src/runtime/shared/schedules-store.mjs +70 -0
  186. package/src/runtime/shared/tool-execution-contract.mjs +104 -0
  187. package/src/runtime/shared/tool-surface.mjs +947 -0
  188. package/src/runtime/shared/user-cwd.mjs +221 -0
  189. package/src/runtime/shared/user-data-guard.mjs +232 -0
  190. package/src/runtime/shared/workspace-router.mjs +259 -0
  191. package/src/standalone/bridge-tool.mjs +1414 -0
  192. package/src/standalone/channel-admin.mjs +366 -0
  193. package/src/standalone/channel-worker-preload.cjs +3 -0
  194. package/src/standalone/channel-worker.mjs +353 -0
  195. package/src/standalone/explore-tool.mjs +233 -0
  196. package/src/standalone/hook-bus.mjs +246 -0
  197. package/src/standalone/plugin-admin.mjs +247 -0
  198. package/src/standalone/provider-admin.mjs +338 -0
  199. package/src/standalone/seeds.mjs +94 -0
  200. package/src/standalone/usage-dashboard.mjs +510 -0
  201. package/src/tui/App.jsx +5446 -0
  202. package/src/tui/components/AnsiText.jsx +199 -0
  203. package/src/tui/components/ContextPanel.jsx +265 -0
  204. package/src/tui/components/Markdown.jsx +205 -0
  205. package/src/tui/components/MarkdownTable.jsx +204 -0
  206. package/src/tui/components/Message.jsx +103 -0
  207. package/src/tui/components/Picker.jsx +317 -0
  208. package/src/tui/components/PromptInput.jsx +584 -0
  209. package/src/tui/components/QueuedCommands.jsx +47 -0
  210. package/src/tui/components/SlashCommandPalette.jsx +114 -0
  211. package/src/tui/components/Spinner.jsx +317 -0
  212. package/src/tui/components/StatusLine.jsx +87 -0
  213. package/src/tui/components/TextEntryPanel.jsx +323 -0
  214. package/src/tui/components/ToolExecution.jsx +791 -0
  215. package/src/tui/components/TurnDone.jsx +78 -0
  216. package/src/tui/components/UsagePanel.jsx +331 -0
  217. package/src/tui/dist/index.mjs +12420 -0
  218. package/src/tui/engine.mjs +2410 -0
  219. package/src/tui/figures.mjs +50 -0
  220. package/src/tui/hooks/useEngine.mjs +16 -0
  221. package/src/tui/index.jsx +254 -0
  222. package/src/tui/input-editing.mjs +242 -0
  223. package/src/tui/markdown/format-token.mjs +194 -0
  224. package/src/tui/paste-attachments.mjs +198 -0
  225. package/src/tui/runtime/shared/process-shutdown.mjs +1 -0
  226. package/src/tui/spinner-verbs.mjs +45 -0
  227. package/src/tui/theme.mjs +67 -0
  228. package/src/tui/time-format.mjs +53 -0
  229. package/src/ui/ansi.mjs +115 -0
  230. package/src/ui/markdown.mjs +195 -0
  231. package/src/ui/statusline.mjs +730 -0
  232. package/src/ui/tool-card.mjs +99 -0
  233. package/src/vendor/statusline/bin/statusline-lib.mjs +805 -0
  234. package/src/vendor/statusline/bin/statusline-route.mjs +596 -0
  235. package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +285 -0
  236. package/src/vendor/statusline/src/gateway/claude-current.mjs +320 -0
  237. package/src/vendor/statusline/src/gateway/route-meta.mjs +753 -0
  238. package/src/vendor/statusline/src/gateway/session-routes.mjs +244 -0
  239. package/src/workflows/default/WORKFLOW.md +7 -0
  240. package/src/workflows/default/workflow.json +14 -0
  241. package/vendor/ink/build/ansi-tokenizer.d.ts +38 -0
  242. package/vendor/ink/build/ansi-tokenizer.js +316 -0
  243. package/vendor/ink/build/ansi-tokenizer.js.map +1 -0
  244. package/vendor/ink/build/colorize.d.ts +3 -0
  245. package/vendor/ink/build/colorize.js +48 -0
  246. package/vendor/ink/build/colorize.js.map +1 -0
  247. package/vendor/ink/build/components/AccessibilityContext.d.ts +3 -0
  248. package/vendor/ink/build/components/AccessibilityContext.js +5 -0
  249. package/vendor/ink/build/components/AccessibilityContext.js.map +1 -0
  250. package/vendor/ink/build/components/AnimationContext.d.ts +9 -0
  251. package/vendor/ink/build/components/AnimationContext.js +13 -0
  252. package/vendor/ink/build/components/AnimationContext.js.map +1 -0
  253. package/vendor/ink/build/components/App.d.ts +24 -0
  254. package/vendor/ink/build/components/App.js +554 -0
  255. package/vendor/ink/build/components/App.js.map +1 -0
  256. package/vendor/ink/build/components/AppContext.d.ts +80 -0
  257. package/vendor/ink/build/components/AppContext.js +25 -0
  258. package/vendor/ink/build/components/AppContext.js.map +1 -0
  259. package/vendor/ink/build/components/BackgroundContext.d.ts +4 -0
  260. package/vendor/ink/build/components/BackgroundContext.js +3 -0
  261. package/vendor/ink/build/components/BackgroundContext.js.map +1 -0
  262. package/vendor/ink/build/components/Box.d.ts +130 -0
  263. package/vendor/ink/build/components/Box.js +34 -0
  264. package/vendor/ink/build/components/Box.js.map +1 -0
  265. package/vendor/ink/build/components/CursorContext.d.ts +11 -0
  266. package/vendor/ink/build/components/CursorContext.js +8 -0
  267. package/vendor/ink/build/components/CursorContext.js.map +1 -0
  268. package/vendor/ink/build/components/ErrorBoundary.d.ts +18 -0
  269. package/vendor/ink/build/components/ErrorBoundary.js +23 -0
  270. package/vendor/ink/build/components/ErrorBoundary.js.map +1 -0
  271. package/vendor/ink/build/components/ErrorOverview.d.ts +6 -0
  272. package/vendor/ink/build/components/ErrorOverview.js +90 -0
  273. package/vendor/ink/build/components/ErrorOverview.js.map +1 -0
  274. package/vendor/ink/build/components/FocusContext.d.ts +16 -0
  275. package/vendor/ink/build/components/FocusContext.js +17 -0
  276. package/vendor/ink/build/components/FocusContext.js.map +1 -0
  277. package/vendor/ink/build/components/Newline.d.ts +13 -0
  278. package/vendor/ink/build/components/Newline.js +8 -0
  279. package/vendor/ink/build/components/Newline.js.map +1 -0
  280. package/vendor/ink/build/components/Spacer.d.ts +7 -0
  281. package/vendor/ink/build/components/Spacer.js +11 -0
  282. package/vendor/ink/build/components/Spacer.js.map +1 -0
  283. package/vendor/ink/build/components/Static.d.ts +24 -0
  284. package/vendor/ink/build/components/Static.js +28 -0
  285. package/vendor/ink/build/components/Static.js.map +1 -0
  286. package/vendor/ink/build/components/StderrContext.d.ts +15 -0
  287. package/vendor/ink/build/components/StderrContext.js +13 -0
  288. package/vendor/ink/build/components/StderrContext.js.map +1 -0
  289. package/vendor/ink/build/components/StdinContext.d.ts +28 -0
  290. package/vendor/ink/build/components/StdinContext.js +20 -0
  291. package/vendor/ink/build/components/StdinContext.js.map +1 -0
  292. package/vendor/ink/build/components/StdoutContext.d.ts +15 -0
  293. package/vendor/ink/build/components/StdoutContext.js +13 -0
  294. package/vendor/ink/build/components/StdoutContext.js.map +1 -0
  295. package/vendor/ink/build/components/Text.d.ts +55 -0
  296. package/vendor/ink/build/components/Text.js +50 -0
  297. package/vendor/ink/build/components/Text.js.map +1 -0
  298. package/vendor/ink/build/components/Transform.d.ts +16 -0
  299. package/vendor/ink/build/components/Transform.js +15 -0
  300. package/vendor/ink/build/components/Transform.js.map +1 -0
  301. package/vendor/ink/build/cursor-helpers.d.ts +39 -0
  302. package/vendor/ink/build/cursor-helpers.js +62 -0
  303. package/vendor/ink/build/cursor-helpers.js.map +1 -0
  304. package/vendor/ink/build/devtools-window-polyfill.d.ts +1 -0
  305. package/vendor/ink/build/devtools-window-polyfill.js +68 -0
  306. package/vendor/ink/build/devtools-window-polyfill.js.map +1 -0
  307. package/vendor/ink/build/devtools.d.ts +1 -0
  308. package/vendor/ink/build/devtools.js +36 -0
  309. package/vendor/ink/build/devtools.js.map +1 -0
  310. package/vendor/ink/build/dom.d.ts +62 -0
  311. package/vendor/ink/build/dom.js +143 -0
  312. package/vendor/ink/build/dom.js.map +1 -0
  313. package/vendor/ink/build/get-max-width.d.ts +3 -0
  314. package/vendor/ink/build/get-max-width.js +10 -0
  315. package/vendor/ink/build/get-max-width.js.map +1 -0
  316. package/vendor/ink/build/hooks/use-animation.d.ts +49 -0
  317. package/vendor/ink/build/hooks/use-animation.js +87 -0
  318. package/vendor/ink/build/hooks/use-animation.js.map +1 -0
  319. package/vendor/ink/build/hooks/use-app.d.ts +5 -0
  320. package/vendor/ink/build/hooks/use-app.js +8 -0
  321. package/vendor/ink/build/hooks/use-app.js.map +1 -0
  322. package/vendor/ink/build/hooks/use-box-metrics.d.ts +59 -0
  323. package/vendor/ink/build/hooks/use-box-metrics.js +81 -0
  324. package/vendor/ink/build/hooks/use-box-metrics.js.map +1 -0
  325. package/vendor/ink/build/hooks/use-cursor.d.ts +12 -0
  326. package/vendor/ink/build/hooks/use-cursor.js +29 -0
  327. package/vendor/ink/build/hooks/use-cursor.js.map +1 -0
  328. package/vendor/ink/build/hooks/use-focus-manager.d.ts +43 -0
  329. package/vendor/ink/build/hooks/use-focus-manager.js +18 -0
  330. package/vendor/ink/build/hooks/use-focus-manager.js.map +1 -0
  331. package/vendor/ink/build/hooks/use-focus.d.ts +30 -0
  332. package/vendor/ink/build/hooks/use-focus.js +43 -0
  333. package/vendor/ink/build/hooks/use-focus.js.map +1 -0
  334. package/vendor/ink/build/hooks/use-input.d.ts +132 -0
  335. package/vendor/ink/build/hooks/use-input.js +126 -0
  336. package/vendor/ink/build/hooks/use-input.js.map +1 -0
  337. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.d.ts +6 -0
  338. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js +12 -0
  339. package/vendor/ink/build/hooks/use-is-screen-reader-enabled.js.map +1 -0
  340. package/vendor/ink/build/hooks/use-paste.d.ts +35 -0
  341. package/vendor/ink/build/hooks/use-paste.js +62 -0
  342. package/vendor/ink/build/hooks/use-paste.js.map +1 -0
  343. package/vendor/ink/build/hooks/use-stderr.d.ts +5 -0
  344. package/vendor/ink/build/hooks/use-stderr.js +8 -0
  345. package/vendor/ink/build/hooks/use-stderr.js.map +1 -0
  346. package/vendor/ink/build/hooks/use-stdin.d.ts +7 -0
  347. package/vendor/ink/build/hooks/use-stdin.js +9 -0
  348. package/vendor/ink/build/hooks/use-stdin.js.map +1 -0
  349. package/vendor/ink/build/hooks/use-stdout.d.ts +5 -0
  350. package/vendor/ink/build/hooks/use-stdout.js +8 -0
  351. package/vendor/ink/build/hooks/use-stdout.js.map +1 -0
  352. package/vendor/ink/build/hooks/use-window-size.d.ts +18 -0
  353. package/vendor/ink/build/hooks/use-window-size.js +22 -0
  354. package/vendor/ink/build/hooks/use-window-size.js.map +1 -0
  355. package/vendor/ink/build/index.d.ts +42 -0
  356. package/vendor/ink/build/index.js +24 -0
  357. package/vendor/ink/build/index.js.map +1 -0
  358. package/vendor/ink/build/ink.d.ts +146 -0
  359. package/vendor/ink/build/ink.js +1022 -0
  360. package/vendor/ink/build/ink.js.map +1 -0
  361. package/vendor/ink/build/input-parser.d.ts +10 -0
  362. package/vendor/ink/build/input-parser.js +194 -0
  363. package/vendor/ink/build/input-parser.js.map +1 -0
  364. package/vendor/ink/build/instances.d.ts +3 -0
  365. package/vendor/ink/build/instances.js +8 -0
  366. package/vendor/ink/build/instances.js.map +1 -0
  367. package/vendor/ink/build/kitty-keyboard.d.ts +23 -0
  368. package/vendor/ink/build/kitty-keyboard.js +32 -0
  369. package/vendor/ink/build/kitty-keyboard.js.map +1 -0
  370. package/vendor/ink/build/log-update.d.ts +20 -0
  371. package/vendor/ink/build/log-update.js +261 -0
  372. package/vendor/ink/build/log-update.js.map +1 -0
  373. package/vendor/ink/build/measure-element.d.ts +20 -0
  374. package/vendor/ink/build/measure-element.js +13 -0
  375. package/vendor/ink/build/measure-element.js.map +1 -0
  376. package/vendor/ink/build/measure-text.d.ts +6 -0
  377. package/vendor/ink/build/measure-text.js +21 -0
  378. package/vendor/ink/build/measure-text.js.map +1 -0
  379. package/vendor/ink/build/output.d.ts +35 -0
  380. package/vendor/ink/build/output.js +328 -0
  381. package/vendor/ink/build/output.js.map +1 -0
  382. package/vendor/ink/build/parse-keypress.d.ts +20 -0
  383. package/vendor/ink/build/parse-keypress.js +495 -0
  384. package/vendor/ink/build/parse-keypress.js.map +1 -0
  385. package/vendor/ink/build/reconciler.d.ts +4 -0
  386. package/vendor/ink/build/reconciler.js +306 -0
  387. package/vendor/ink/build/reconciler.js.map +1 -0
  388. package/vendor/ink/build/render-background.d.ts +4 -0
  389. package/vendor/ink/build/render-background.js +25 -0
  390. package/vendor/ink/build/render-background.js.map +1 -0
  391. package/vendor/ink/build/render-border.d.ts +4 -0
  392. package/vendor/ink/build/render-border.js +84 -0
  393. package/vendor/ink/build/render-border.js.map +1 -0
  394. package/vendor/ink/build/render-node-to-output.d.ts +14 -0
  395. package/vendor/ink/build/render-node-to-output.js +162 -0
  396. package/vendor/ink/build/render-node-to-output.js.map +1 -0
  397. package/vendor/ink/build/render-to-string.d.ts +38 -0
  398. package/vendor/ink/build/render-to-string.js +116 -0
  399. package/vendor/ink/build/render-to-string.js.map +1 -0
  400. package/vendor/ink/build/render.d.ts +176 -0
  401. package/vendor/ink/build/render.js +71 -0
  402. package/vendor/ink/build/render.js.map +1 -0
  403. package/vendor/ink/build/renderer.d.ts +8 -0
  404. package/vendor/ink/build/renderer.js +64 -0
  405. package/vendor/ink/build/renderer.js.map +1 -0
  406. package/vendor/ink/build/sanitize-ansi.d.ts +2 -0
  407. package/vendor/ink/build/sanitize-ansi.js +27 -0
  408. package/vendor/ink/build/sanitize-ansi.js.map +1 -0
  409. package/vendor/ink/build/squash-text-nodes.d.ts +3 -0
  410. package/vendor/ink/build/squash-text-nodes.js +36 -0
  411. package/vendor/ink/build/squash-text-nodes.js.map +1 -0
  412. package/vendor/ink/build/styles.d.ts +302 -0
  413. package/vendor/ink/build/styles.js +303 -0
  414. package/vendor/ink/build/styles.js.map +1 -0
  415. package/vendor/ink/build/utils.d.ts +9 -0
  416. package/vendor/ink/build/utils.js +19 -0
  417. package/vendor/ink/build/utils.js.map +1 -0
  418. package/vendor/ink/build/wrap-text.d.ts +3 -0
  419. package/vendor/ink/build/wrap-text.js +38 -0
  420. package/vendor/ink/build/wrap-text.js.map +1 -0
  421. package/vendor/ink/build/write-synchronized.d.ts +4 -0
  422. package/vendor/ink/build/write-synchronized.js +9 -0
  423. package/vendor/ink/build/write-synchronized.js.map +1 -0
  424. package/vendor/ink/license +10 -0
  425. package/vendor/ink/package.json +137 -0
  426. package/.claude-plugin/marketplace.json +0 -34
  427. package/.claude-plugin/plugin.json +0 -20
  428. package/.gitattributes +0 -34
  429. package/.mcp.json +0 -14
  430. package/ARCHITECTURE.md +0 -77
  431. package/CHANGELOG.md +0 -30
  432. package/CONTRIBUTING.md +0 -45
  433. package/DATA-FLOW.md +0 -79
  434. package/LICENSE +0 -21
  435. package/SECURITY.md +0 -138
  436. package/UNINSTALL.md +0 -115
  437. package/agents/maintenance.md +0 -5
  438. package/agents/memory-classification.md +0 -30
  439. package/agents/scheduler-task.md +0 -18
  440. package/agents/webhook-handler.md +0 -27
  441. package/agents/worker.md +0 -24
  442. package/bin/bridge +0 -133
  443. package/bin/statusline-launcher.mjs +0 -82
  444. package/bin/statusline-lib.mjs +0 -581
  445. package/bin/statusline-route.mjs +0 -273
  446. package/bin/statusline.mjs +0 -638
  447. package/bun.lock +0 -927
  448. package/commands/config.md +0 -16
  449. package/commands/doctor.md +0 -13
  450. package/commands/model.md +0 -61
  451. package/commands/setup.md +0 -17
  452. package/defaults/hidden-roles.json +0 -68
  453. package/defaults/memory-chunk-prompt.md +0 -63
  454. package/defaults/mixdog-config.template.json +0 -27
  455. package/defaults/user-workflow.json +0 -8
  456. package/defaults/user-workflow.md +0 -17
  457. package/hooks/hooks.json +0 -73
  458. package/hooks/lib/active-instance.cjs +0 -77
  459. package/hooks/lib/permission-evaluator.cjs +0 -411
  460. package/hooks/lib/permission-route.cjs +0 -63
  461. package/hooks/lib/settings-loader.cjs +0 -117
  462. package/hooks/post-tool-use.cjs +0 -84
  463. package/hooks/pre-mcp-sandbox.cjs +0 -158
  464. package/hooks/pre-tool-subagent.cjs +0 -258
  465. package/hooks/session-start.cjs +0 -1493
  466. package/hooks/shim-launcher.cjs +0 -65
  467. package/hooks/turn-timer.cjs +0 -82
  468. package/lib/claude-md-writer.cjs +0 -386
  469. package/lib/keychain-cjs.cjs +0 -290
  470. package/lib/plugin-paths.cjs +0 -69
  471. package/lib/rules-builder.cjs +0 -241
  472. package/native/README.md +0 -117
  473. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  474. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  475. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  476. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  477. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  478. package/prompts/code-review.txt +0 -16
  479. package/prompts/security-audit.txt +0 -17
  480. package/rules/bridge/00-common.md +0 -39
  481. package/rules/bridge/20-skip-protocol.md +0 -18
  482. package/rules/bridge/30-explorer.md +0 -33
  483. package/rules/bridge/40-cycle1-agent.md +0 -52
  484. package/rules/bridge/41-cycle2-agent.md +0 -62
  485. package/rules/lead/00-tool-lead.md +0 -61
  486. package/rules/lead/01-general.md +0 -26
  487. package/rules/lead/02-channels.md +0 -49
  488. package/rules/lead/03-team.md +0 -27
  489. package/rules/lead/04-workflow.md +0 -20
  490. package/rules/shared/00-language.md +0 -14
  491. package/rules/shared/01-tool.md +0 -138
  492. package/scripts/bootstrap.mjs +0 -130
  493. package/scripts/bridge-unify-smoke.mjs +0 -308
  494. package/scripts/build-runtime-linux.sh +0 -348
  495. package/scripts/build-runtime-macos.sh +0 -217
  496. package/scripts/build-runtime-windows.ps1 +0 -242
  497. package/scripts/builtin-utils-smoke.mjs +0 -398
  498. package/scripts/bump.mjs +0 -80
  499. package/scripts/check-json.mjs +0 -45
  500. package/scripts/check-syntax-changed.mjs +0 -102
  501. package/scripts/check-syntax.mjs +0 -58
  502. package/scripts/code-graph-batch.test.mjs +0 -33
  503. package/scripts/config-preserve-smoke.mjs +0 -180
  504. package/scripts/doctor.mjs +0 -489
  505. package/scripts/edit-normalize-fuzz.mjs +0 -130
  506. package/scripts/edit-normalize-smoke.mjs +0 -401
  507. package/scripts/edit-operation-smoke.mjs +0 -369
  508. package/scripts/edit2-smoke.mjs +0 -63
  509. package/scripts/ensure-deps.mjs +0 -259
  510. package/scripts/fuzzy-e2e.mjs +0 -28
  511. package/scripts/fuzzy-smoke.mjs +0 -26
  512. package/scripts/gateway-model.mjs +0 -596
  513. package/scripts/generate-runtime-manifest.mjs +0 -166
  514. package/scripts/guard-smoke.mjs +0 -66
  515. package/scripts/hidden-role-schema-smoke.mjs +0 -162
  516. package/scripts/hook-routing-smoke.mjs +0 -29
  517. package/scripts/inject-input.ps1 +0 -204
  518. package/scripts/io-complex-smoke.mjs +0 -667
  519. package/scripts/io-explore-bench.mjs +0 -424
  520. package/scripts/io-guardrails-smoke.mjs +0 -205
  521. package/scripts/io-mini-bench-baseline.json +0 -11
  522. package/scripts/io-mini-bench.mjs +0 -216
  523. package/scripts/io-route-harness.mjs +0 -933
  524. package/scripts/io-telemetry-report.mjs +0 -691
  525. package/scripts/lib/gateway-inventory.mjs +0 -178
  526. package/scripts/lib/gateway-settings.mjs +0 -78
  527. package/scripts/mutation-bench.mjs +0 -564
  528. package/scripts/mutation-io-smoke.mjs +0 -1097
  529. package/scripts/native-patch-bridge-smoke.mjs +0 -288
  530. package/scripts/native-patch-smoke.mjs +0 -304
  531. package/scripts/openai-oauth-catalog-smoke.mjs +0 -53
  532. package/scripts/patch-interior-context-smoke.mjs +0 -49
  533. package/scripts/patch-newline-utf8-smoke.mjs +0 -157
  534. package/scripts/perf-hook-smoke.mjs +0 -71
  535. package/scripts/permission-eval-smoke.mjs +0 -443
  536. package/scripts/prep-patch.mjs +0 -53
  537. package/scripts/prep-shim.mjs +0 -96
  538. package/scripts/provider-cache-smoke.mjs +0 -687
  539. package/scripts/report-runtime-health.mjs +0 -132
  540. package/scripts/resolve-bun.mjs +0 -60
  541. package/scripts/run-mcp.mjs +0 -1473
  542. package/scripts/salvage-v4a-shatter.test.mjs +0 -58
  543. package/scripts/scoped-cache-io-smoke.mjs +0 -103
  544. package/scripts/shell-policy-round3-smoke.mjs +0 -46
  545. package/scripts/smoke-runtime-negative.ps1 +0 -100
  546. package/scripts/smoke-runtime-negative.sh +0 -95
  547. package/scripts/stall-policy-smoke.mjs +0 -50
  548. package/scripts/start-memory-worker.mjs +0 -23
  549. package/scripts/statusline-launcher-smoke.mjs +0 -235
  550. package/scripts/stress-atomic-write.mjs +0 -1028
  551. package/scripts/test-fault-inject.mjs +0 -164
  552. package/scripts/test-large-file.mjs +0 -174
  553. package/scripts/tool-edge-smoke.mjs +0 -209
  554. package/scripts/uninstall.mjs +0 -238
  555. package/scripts/webhook-selfheal-smoke.mjs +0 -27
  556. package/scripts/write-overwrite-guard-smoke.mjs +0 -56
  557. package/server-main.mjs +0 -3350
  558. package/server.mjs +0 -468
  559. package/setup/config-merge.mjs +0 -246
  560. package/setup/install.mjs +0 -574
  561. package/setup/launch-core.mjs +0 -617
  562. package/setup/launch.mjs +0 -101
  563. package/setup/locate-claude.mjs +0 -56
  564. package/setup/mixdog-cli.mjs +0 -122
  565. package/setup/setup-server.mjs +0 -3305
  566. package/setup/setup.html +0 -3740
  567. package/setup/tui.mjs +0 -325
  568. package/skills/retro-skill-proposer/SKILL.md +0 -92
  569. package/skills/schedule-add/SKILL.md +0 -77
  570. package/skills/setup/SKILL.md +0 -356
  571. package/skills/webhook-add/SKILL.md +0 -81
  572. package/src/agent/bridge-stall-watchdog.mjs +0 -337
  573. package/src/agent/index.mjs +0 -2229
  574. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +0 -1010
  575. package/src/agent/orchestrator/bridge-retry.mjs +0 -220
  576. package/src/agent/orchestrator/bridge-trace.mjs +0 -601
  577. package/src/agent/orchestrator/cache-mtime.mjs +0 -58
  578. package/src/agent/orchestrator/config.mjs +0 -405
  579. package/src/agent/orchestrator/context/collect.mjs +0 -651
  580. package/src/agent/orchestrator/dispatch-persist.mjs +0 -549
  581. package/src/agent/orchestrator/drain-registry.mjs +0 -50
  582. package/src/agent/orchestrator/explore-validator.mjs +0 -8
  583. package/src/agent/orchestrator/internal-roles.mjs +0 -118
  584. package/src/agent/orchestrator/internal-tools.mjs +0 -88
  585. package/src/agent/orchestrator/jobs.mjs +0 -116
  586. package/src/agent/orchestrator/mcp/client.mjs +0 -364
  587. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +0 -1884
  588. package/src/agent/orchestrator/providers/anthropic.mjs +0 -598
  589. package/src/agent/orchestrator/providers/gemini.mjs +0 -1530
  590. package/src/agent/orchestrator/providers/grok-oauth.mjs +0 -779
  591. package/src/agent/orchestrator/providers/model-catalog.mjs +0 -374
  592. package/src/agent/orchestrator/providers/openai-compat-stream.mjs +0 -366
  593. package/src/agent/orchestrator/providers/openai-compat.mjs +0 -1511
  594. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -1891
  595. package/src/agent/orchestrator/providers/openai-oauth.mjs +0 -1456
  596. package/src/agent/orchestrator/providers/openai-ws.mjs +0 -127
  597. package/src/agent/orchestrator/providers/registry.mjs +0 -192
  598. package/src/agent/orchestrator/providers/retry-classifier.mjs +0 -325
  599. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +0 -42
  600. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +0 -142
  601. package/src/agent/orchestrator/session/cache/read-cache.mjs +0 -319
  602. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +0 -361
  603. package/src/agent/orchestrator/session/loop.mjs +0 -1619
  604. package/src/agent/orchestrator/session/manager.mjs +0 -1991
  605. package/src/agent/orchestrator/session/result-classification.mjs +0 -65
  606. package/src/agent/orchestrator/session/store.mjs +0 -632
  607. package/src/agent/orchestrator/session/stream-watchdog.mjs +0 -130
  608. package/src/agent/orchestrator/session/tool-result-offload.mjs +0 -166
  609. package/src/agent/orchestrator/session/trim.mjs +0 -491
  610. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +0 -115
  611. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +0 -331
  612. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +0 -150
  613. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +0 -228
  614. package/src/agent/orchestrator/smart-bridge/index.mjs +0 -215
  615. package/src/agent/orchestrator/smart-bridge/profiles.mjs +0 -37
  616. package/src/agent/orchestrator/smart-bridge/registry.mjs +0 -348
  617. package/src/agent/orchestrator/stall-policy.mjs +0 -201
  618. package/src/agent/orchestrator/tool-loop-guard.mjs +0 -75
  619. package/src/agent/orchestrator/tools/bash-session.mjs +0 -722
  620. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +0 -171
  621. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +0 -511
  622. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +0 -480
  623. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +0 -256
  624. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +0 -58
  625. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +0 -240
  626. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +0 -113
  627. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +0 -74
  628. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +0 -242
  629. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +0 -211
  630. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +0 -1364
  631. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +0 -126
  632. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +0 -141
  633. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +0 -194
  634. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +0 -60
  635. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +0 -168
  636. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +0 -173
  637. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +0 -48
  638. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -99
  639. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +0 -193
  640. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +0 -597
  641. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
  642. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +0 -300
  643. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +0 -152
  644. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +0 -118
  645. package/src/agent/orchestrator/tools/builtin/read-open.mjs +0 -190
  646. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +0 -728
  647. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +0 -173
  648. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +0 -602
  649. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +0 -196
  650. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +0 -422
  651. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +0 -158
  652. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +0 -869
  653. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +0 -962
  654. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +0 -223
  655. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +0 -206
  656. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +0 -401
  657. package/src/agent/orchestrator/tools/builtin.mjs +0 -503
  658. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +0 -24
  659. package/src/agent/orchestrator/tools/code-graph.mjs +0 -4095
  660. package/src/agent/orchestrator/tools/cwd-tool.mjs +0 -298
  661. package/src/agent/orchestrator/tools/destructive-warning.mjs +0 -323
  662. package/src/agent/orchestrator/tools/edit-normalize.mjs +0 -603
  663. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +0 -154
  664. package/src/agent/orchestrator/tools/graph-manifest.json +0 -26
  665. package/src/agent/orchestrator/tools/host-input.mjs +0 -204
  666. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +0 -143
  667. package/src/agent/orchestrator/tools/patch-manifest.json +0 -26
  668. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +0 -20
  669. package/src/agent/orchestrator/tools/patch.mjs +0 -2754
  670. package/src/agent/orchestrator/tools/progress-message.mjs +0 -118
  671. package/src/agent/orchestrator/tools/shell-command.mjs +0 -865
  672. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +0 -7
  673. package/src/agent/orchestrator/tools/shell-snapshot.mjs +0 -313
  674. package/src/agent/orchestrator/workflow-store.mjs +0 -93
  675. package/src/agent/tool-defs.mjs +0 -110
  676. package/src/channels/backends/discord.mjs +0 -784
  677. package/src/channels/data/voice-runtime-manifest.json +0 -138
  678. package/src/channels/index.mjs +0 -3268
  679. package/src/channels/lib/config.mjs +0 -292
  680. package/src/channels/lib/drop-trace.mjs +0 -71
  681. package/src/channels/lib/event-pipeline.mjs +0 -81
  682. package/src/channels/lib/holidays.mjs +0 -138
  683. package/src/channels/lib/hook-pipe-server.mjs +0 -822
  684. package/src/channels/lib/output-forwarder.mjs +0 -765
  685. package/src/channels/lib/runtime-paths.mjs +0 -552
  686. package/src/channels/lib/scheduler.mjs +0 -723
  687. package/src/channels/lib/session-discovery.mjs +0 -103
  688. package/src/channels/lib/state-file.mjs +0 -68
  689. package/src/channels/lib/status-snapshot.mjs +0 -219
  690. package/src/channels/lib/tool-format.mjs +0 -140
  691. package/src/channels/lib/transcript-discovery.mjs +0 -195
  692. package/src/channels/lib/voice-runtime-fetcher.mjs +0 -734
  693. package/src/channels/lib/webhook.mjs +0 -1318
  694. package/src/channels/tool-defs.mjs +0 -170
  695. package/src/daemon/host.mjs +0 -118
  696. package/src/daemon/mcp-transport.mjs +0 -47
  697. package/src/daemon/session.mjs +0 -100
  698. package/src/daemon/thin-client.mjs +0 -71
  699. package/src/daemon/transport.mjs +0 -163
  700. package/src/gateway/claude-current.mjs +0 -255
  701. package/src/gateway/oauth-usage.mjs +0 -598
  702. package/src/gateway/route-meta.mjs +0 -629
  703. package/src/gateway/server.mjs +0 -713
  704. package/src/memory/data/runtime-manifest.json +0 -40
  705. package/src/memory/index.mjs +0 -3332
  706. package/src/memory/lib/core-memory-store.mjs +0 -330
  707. package/src/memory/lib/embedding-provider.mjs +0 -269
  708. package/src/memory/lib/embedding-worker.mjs +0 -323
  709. package/src/memory/lib/memory-cycle1.mjs +0 -645
  710. package/src/memory/lib/memory-cycle2.mjs +0 -1284
  711. package/src/memory/lib/memory-cycle3.mjs +0 -540
  712. package/src/memory/lib/memory-embed.mjs +0 -299
  713. package/src/memory/lib/memory-ops-policy.mjs +0 -190
  714. package/src/memory/lib/memory-recall-store.mjs +0 -638
  715. package/src/memory/lib/memory.mjs +0 -412
  716. package/src/memory/lib/pg/adapter.mjs +0 -308
  717. package/src/memory/lib/pg/process.mjs +0 -360
  718. package/src/memory/lib/pg/supervisor.mjs +0 -396
  719. package/src/memory/lib/runtime-fetcher.mjs +0 -458
  720. package/src/memory/lib/trace-store.mjs +0 -728
  721. package/src/memory/tool-defs.mjs +0 -79
  722. package/src/search/index.mjs +0 -1173
  723. package/src/search/lib/backends/anthropic-oauth.mjs +0 -98
  724. package/src/search/lib/backends/exa.mjs +0 -50
  725. package/src/search/lib/backends/firecrawl.mjs +0 -61
  726. package/src/search/lib/backends/gemini-api.mjs +0 -83
  727. package/src/search/lib/backends/grok-oauth.mjs +0 -86
  728. package/src/search/lib/backends/index.mjs +0 -150
  729. package/src/search/lib/backends/openai-api.mjs +0 -144
  730. package/src/search/lib/backends/openai-oauth.mjs +0 -102
  731. package/src/search/lib/backends/openai-web-search.mjs +0 -76
  732. package/src/search/lib/backends/tavily.mjs +0 -55
  733. package/src/search/lib/backends/xai-api.mjs +0 -113
  734. package/src/search/lib/config.mjs +0 -192
  735. package/src/search/lib/provider-usage.mjs +0 -67
  736. package/src/search/lib/providers.mjs +0 -47
  737. package/src/search/lib/search-intent.mjs +0 -109
  738. package/src/search/lib/setup-handler.mjs +0 -261
  739. package/src/search/lib/web-tools.mjs +0 -1219
  740. package/src/search/tool-defs.mjs +0 -83
  741. package/src/setup/defender-exclusion.mjs +0 -183
  742. package/src/shared/atomic-file.mjs +0 -436
  743. package/src/shared/config.mjs +0 -372
  744. package/src/shared/daemon-recycle.mjs +0 -108
  745. package/src/shared/disable-claude-builtins.mjs +0 -91
  746. package/src/shared/err-text.mjs +0 -12
  747. package/src/shared/llm/http-agent.mjs +0 -123
  748. package/src/shared/open-url.mjs +0 -62
  749. package/src/shared/plugin-paths.mjs +0 -58
  750. package/src/shared/schedules-store.mjs +0 -70
  751. package/src/shared/seed.mjs +0 -161
  752. package/src/shared/user-cwd.mjs +0 -225
  753. package/src/shared/user-data-guard.mjs +0 -244
  754. package/src/status/aggregator.mjs +0 -584
  755. package/src/status/server.mjs +0 -413
  756. package/tools.json +0 -1671
  757. /package/{defaults → src/defaults}/cycle3-review-prompt.md +0 -0
  758. /package/{defaults → src/defaults}/memory-promote-prompt.md +0 -0
  759. /package/{hooks → src/hooks}/lib/permission-rules.cjs +0 -0
  760. /package/{lib → src/lib}/config-cjs.cjs +0 -0
  761. /package/{lib → src/lib}/hook-pipe-path.cjs +0 -0
  762. /package/{lib → src/lib}/mixdog-debug.cjs +0 -0
  763. /package/{lib → src/lib}/text-utils.cjs +0 -0
  764. /package/{rules → src/rules}/bridge/42-cycle3-agent.md +0 -0
  765. /package/src/{agent → runtime/agent}/orchestrator/activity-bus.mjs +0 -0
  766. /package/src/{agent → runtime/agent}/orchestrator/providers/anthropic-betas.mjs +0 -0
  767. /package/src/{agent → runtime/agent}/orchestrator/session/abort-lookup.mjs +0 -0
  768. /package/src/{agent → runtime/agent}/orchestrator/session/cache/scoped-cache-outcome.mjs +0 -0
  769. /package/src/{agent → runtime/agent}/orchestrator/session/cache/util.mjs +0 -0
  770. /package/src/{agent → runtime/agent}/orchestrator/session/read-dedup.mjs +0 -0
  771. /package/src/{agent → runtime/agent}/orchestrator/session/save-session-worker.mjs +0 -0
  772. /package/src/{agent → runtime/agent}/orchestrator/smart-bridge/session-builder.mjs +0 -0
  773. /package/src/{agent → runtime/agent}/orchestrator/tools/bash-policy-scan.mjs +0 -0
  774. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/atomic-write.mjs +0 -0
  775. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/binary-file.mjs +0 -0
  776. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cache-layers.mjs +0 -0
  777. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/cwd-utils.mjs +0 -0
  778. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/device-paths.mjs +0 -0
  779. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -0
  780. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/diff-utils.mjs +0 -0
  781. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/fs-reachability.mjs +0 -0
  782. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/grep-formatting.mjs +0 -0
  783. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/hash-utils.mjs +0 -0
  784. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/list-formatting.mjs +0 -0
  785. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/open-config-tool.mjs +0 -0
  786. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-locks.mjs +0 -0
  787. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/path-utils.mjs +0 -0
  788. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-args.mjs +0 -0
  789. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-batch.mjs +0 -0
  790. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-constants.mjs +0 -0
  791. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image-resize.mjs +0 -0
  792. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-image.mjs +0 -0
  793. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-lines.mjs +0 -0
  794. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-mode-tool.mjs +0 -0
  795. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-range-index.mjs +0 -0
  796. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-ranges.mjs +0 -0
  797. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-special-files.mjs +0 -0
  798. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-tool.mjs +0 -0
  799. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/read-windows.mjs +0 -0
  800. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-analysis.mjs +0 -0
  801. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/shell-output.mjs +0 -0
  802. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-helpers.mjs +0 -0
  803. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/snapshot-validation.mjs +0 -0
  804. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/text-stats.mjs +0 -0
  805. /package/src/{agent → runtime/agent}/orchestrator/tools/builtin/windows-roots.mjs +0 -0
  806. /package/src/{agent → runtime/agent}/orchestrator/tools/code-graph-prewarm-worker.mjs +0 -0
  807. /package/src/{agent → runtime/agent}/orchestrator/tools/env-scrub.mjs +0 -0
  808. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-content-cache.mjs +0 -0
  809. /package/src/{agent → runtime/agent}/orchestrator/tools/mutation-planner.mjs +0 -0
  810. /package/src/{agent → runtime/agent}/orchestrator/tools/next-call-utils.mjs +0 -0
  811. /package/src/{agent → runtime/agent}/orchestrator/tools/result-compression.mjs +0 -0
  812. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-exec-policy.mjs +0 -0
  813. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy-danger-target.mjs +0 -0
  814. /package/src/{agent → runtime/agent}/orchestrator/tools/shell-policy.mjs +0 -0
  815. /package/src/{channels → runtime/channels}/lib/cli-worker-host.mjs +0 -0
  816. /package/src/{channels → runtime/channels}/lib/config-lock.mjs +0 -0
  817. /package/src/{channels → runtime/channels}/lib/event-queue.mjs +0 -0
  818. /package/src/{channels → runtime/channels}/lib/executor.mjs +0 -0
  819. /package/src/{channels → runtime/channels}/lib/format.mjs +0 -0
  820. /package/src/{channels → runtime/channels}/lib/interaction-workflows.mjs +0 -0
  821. /package/src/{channels → runtime/channels}/lib/memory-client.mjs +0 -0
  822. /package/src/{channels → runtime/channels}/lib/session-control.mjs +0 -0
  823. /package/src/{channels → runtime/channels}/lib/settings.mjs +0 -0
  824. /package/src/{channels → runtime/channels}/lib/whisper-server.mjs +0 -0
  825. /package/src/{memory → runtime/memory}/lib/agent-ipc.mjs +0 -0
  826. /package/src/{memory → runtime/memory}/lib/bridge-trace-queries.mjs +0 -0
  827. /package/src/{memory → runtime/memory}/lib/llm-worker-host.mjs +0 -0
  828. /package/src/{memory → runtime/memory}/lib/memory-cycle.mjs +0 -0
  829. /package/src/{memory → runtime/memory}/lib/memory-extraction.mjs +0 -0
  830. /package/src/{memory → runtime/memory}/lib/memory-maintenance-store.mjs +0 -0
  831. /package/src/{memory → runtime/memory}/lib/memory-recall-id-patch.mjs +0 -0
  832. /package/src/{memory → runtime/memory}/lib/memory-recall-read-query.mjs +0 -0
  833. /package/src/{memory → runtime/memory}/lib/memory-recall-scope-filter.mjs +0 -0
  834. /package/src/{memory → runtime/memory}/lib/memory-retrievers.mjs +0 -0
  835. /package/src/{memory → runtime/memory}/lib/memory-score.mjs +0 -0
  836. /package/src/{memory → runtime/memory}/lib/memory-text-utils.mjs +0 -0
  837. /package/src/{memory → runtime/memory}/lib/model-profile.mjs +0 -0
  838. /package/src/{memory → runtime/memory}/lib/project-id-resolver.mjs +0 -0
  839. /package/src/{search → runtime/search}/lib/cache.mjs +0 -0
  840. /package/src/{search → runtime/search}/lib/formatter.mjs +0 -0
  841. /package/src/{search → runtime/search}/lib/state.mjs +0 -0
  842. /package/src/{shared → runtime/shared}/abort-controller.mjs +0 -0
  843. /package/src/{shared → runtime/shared}/llm/cost.mjs +0 -0
  844. /package/src/{shared → runtime/shared}/llm/index.mjs +0 -0
  845. /package/src/{shared → runtime/shared}/llm/pid-cleanup.mjs +0 -0
  846. /package/src/{shared → runtime/shared}/llm/usage-log.mjs +0 -0
  847. /package/src/{shared → runtime/shared}/wsl.mjs +0 -0
@@ -0,0 +1,1389 @@
1
+ const __mixdogMemoryStderrWrite = process.stderr.write.bind(process.stderr);
2
+ function __mixdogMemoryLog(...args) {
3
+ if (process.env.MIXDOG_QUIET_MEMORY_LOG) return true;
4
+ return __mixdogMemoryStderrWrite(...args);
5
+ }
6
+
7
+ import { existsSync, readFileSync } from 'fs'
8
+ import { join } from 'path'
9
+ import { fileURLToPath } from 'url'
10
+ import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
11
+ import { callBridgeLlm } from './agent-ipc.mjs'
12
+ import {
13
+ syncRootEmbedding, deleteRootEmbedding, flushEmbeddingDirty,
14
+ } from './memory-embed.mjs'
15
+ import { listCore, backfillCoreEmbeddings, CORE_SUMMARY_MAX } from './core-memory-store.mjs'
16
+ import { markCycleRequest, consumeCycleRequests, resolveCoalesceMaxDrains, scheduleCoalescedCycleRetry, makeCycleRequestSignature, resolveCoalesceMaxRetries } from './memory-cycle-requests.mjs'
17
+
18
+ export const CYCLE2_ACTIVE_TARGET_CAP = 100
19
+ const TIER1_THRESHOLD = 0.78
20
+
21
+ const TIER2_LOW = 0.65
22
+ const LLM_JUDGE_CAP = 20
23
+
24
+ function throwIfAborted(signal) {
25
+ if (signal?.aborted) throw signal.reason ?? new Error('aborted')
26
+ }
27
+
28
+ // Status-based verb whitelist. 3-tier policy: pending → active/archived,
29
+ // active → active/archived/update/merge.
30
+ const STATUS_ALLOWED_VERBS = {
31
+ pending: new Set(['active', 'archived']),
32
+ active: new Set(['active', 'archived', 'update', 'merge']),
33
+ }
34
+ const NON_ARCHIVE_VERBS = new Set(['active', 'update', 'merge'])
35
+ // Union of every primary (status) verb across all statuses, plus the two
36
+ // non-verb line kinds. Used by the stray-index shift guard to decide whether
37
+ // a `idx|id|verb` line had a leading row index prepended by the LLM.
38
+ const ALL_PRIMARY_VERBS = new Set(['active', 'archived', 'update', 'merge'])
39
+ const isShiftFollowToken = (tok) => {
40
+ const v = String(tok ?? '').trim().toLowerCase()
41
+ return ALL_PRIMARY_VERBS.has(v) || v === 'why' || v === 'core'
42
+ }
43
+
44
+ function resourceDir() {
45
+ return process.env.MIXDOG_ROOT || fileURLToPath(new URL('../../../..', import.meta.url))
46
+ }
47
+
48
+ async function invokeLlm(prompt, mode, preset, timeout, llmCall = callBridgeLlm) {
49
+ return await llmCall({
50
+ role: 'cycle2-agent',
51
+ taskType: 'maintenance',
52
+ mode,
53
+ preset,
54
+ timeout,
55
+ cwd: null,
56
+ }, prompt)
57
+ }
58
+
59
+ function buildPidMap(rowSets) {
60
+ const pids = [...new Set(rowSets.flat().map(r => r.project_id).filter(Boolean))].sort()
61
+ return new Map(pids.map((p, i) => [p, `P${i + 1}`]))
62
+ }
63
+
64
+ function formatEntriesForPromotePrompt(rows, pidMap, opts = {}) {
65
+ if (!rows || rows.length === 0) return '(none)'
66
+ const map = pidMap ?? buildPidMap([rows])
67
+ // When numbered, prefix each row with its 1-based prompt-order ordinal so the
68
+ // gate LLM can echo a row number it can see, instead of inventing one. The
69
+ // ordinal domain (1..N) and the 5-digit batch-id domain must stay disjoint —
70
+ // see the ordinalToId invariant in runUnifiedGate.
71
+ const numbered = opts.numbered === true
72
+ const lines = rows.map((r, i) => {
73
+ const tag = r.project_id ? (map.get(r.project_id) ?? 'C') : 'C'
74
+ const stat = r.status ? `[${r.status}]` : '[?]'
75
+ const prefix = numbered ? `${i + 1}. ` : '- '
76
+ return `${prefix}id:${r.id} ${stat} ${tag} ${r.category} s:${r.score ?? 'n'} el:${r.element} sm:${String(r.summary || '').slice(0, 100)}`
77
+ })
78
+ if (map.size === 0) return lines.join('\n')
79
+ const legend = [...map.entries()].map(([p, t]) => `${t}=${p}`).concat('C=COMMON').join(', ')
80
+ return `# pid: ${legend}\n` + lines.join('\n')
81
+ }
82
+
83
+ // User-curated rows from core_entries — id-less, no status, no score; the
84
+ // LLM only needs element + summary + project tag to detect overlap with
85
+ // candidate entries below. Format kept terse so the prompt budget stays small.
86
+ function formatUserCoreForPrompt(rows, pidMap) {
87
+ if (!rows || rows.length === 0) return '(none)'
88
+ const map = pidMap ?? new Map()
89
+ return rows.map(r => {
90
+ const tag = r.project_id ? (map.get(r.project_id) ?? 'C') : 'C'
91
+ const sm = String(r.summary || '').slice(0, 200)
92
+ return `- ${tag} ${r.category}: ${r.element}${sm && sm !== r.element ? ` — ${sm}` : ''}`
93
+ }).join('\n')
94
+ }
95
+
96
+ // Parse pipe-format unified verdicts. Each line: <id>|<verb> [|...].
97
+ // Verbs validated against the row's current status via STATUS_ALLOWED_VERBS.
98
+ // Returns { actions, rejected } or null when no parseable lines.
99
+ function parseUnifiedFormat(raw, statusById, ordinalToId = null) {
100
+ if (raw == null) return null
101
+ const text = String(raw).trim()
102
+ if (!text) return { actions: [], rejected: new Set() }
103
+ const lines = text.split('\n')
104
+ const actions = []
105
+ const rejected = new Set()
106
+ const support = new Map()
107
+ let sawValid = false
108
+ // Resolve a first-field/merge token to a real batch id. The gate may echo
109
+ // either the exact 5-digit batch id OR the 1-based row ordinal shown in the
110
+ // numbered Entries block. The two domains are disjoint (asserted in
111
+ // runUnifiedGate), so an exact-id hit always wins and an unmatched value
112
+ // falls back to ordinal lookup; anything else is NaN (line treated invalid).
113
+ const resolveId = (tok) => {
114
+ const n = Number(String(tok ?? '').trim())
115
+ if (!Number.isFinite(n)) return NaN
116
+ if (statusById.has(n)) return n
117
+ if (ordinalToId && ordinalToId.has(n)) return ordinalToId.get(n)
118
+ return NaN
119
+ }
120
+ for (const rawLine of lines) {
121
+ const line = rawLine.trim()
122
+ if (!line) continue
123
+ if (line.startsWith('//') || line.startsWith('#')) continue
124
+ if (line.startsWith('```')) continue
125
+ const parts = line.split('|')
126
+ if (parts.length < 2) continue
127
+ // LLM sometimes prefixes a row index, emitting `idx|id|verdict` instead of
128
+ // `id|verdict`; parts[0] (the index) is a stray token and the line must be
129
+ // shifted before parsing. Strict invariant so a real 2-field `id|verdict`
130
+ // is never shifted into a 1-field line (which would throw on parts[1]):
131
+ // parts.length >= 3 AND parts[1] is a known batch id AND parts[2] is a
132
+ // valid primary verb / why / core (the shifted verdict slot).
133
+ // Trigger when EITHER parts[0] is not a known id (classic stray index) OR
134
+ // parts[0] IS known but parts[1] is not itself a valid verb — that covers
135
+ // `1|1|active`, where the stray index collides with a real batch id and the
136
+ // un-shifted reading would verb-reject the wrong row.
137
+ if (
138
+ parts.length >= 3 &&
139
+ statusById.has(Number(parts[1].trim())) &&
140
+ isShiftFollowToken(parts[2]) &&
141
+ (!statusById.has(Number(parts[0].trim())) || !isShiftFollowToken(parts[1]))
142
+ ) {
143
+ parts.shift()
144
+ }
145
+ const entryId = resolveId(parts[0])
146
+ const action = parts[1].trim().toLowerCase()
147
+ if (!Number.isFinite(entryId) || !action) continue
148
+ const status = statusById.get(entryId)
149
+ if (!status) continue
150
+ // Only mark as parse-ok when the id is known to the batch; a response
151
+ // composed entirely of unknown ids would otherwise return parse-ok with
152
+ // zero actions/rejections, leaving the rows un-reviewed and re-queued.
153
+ sawValid = true
154
+ if (action === 'core') {
155
+ actions.push({ entry_id: entryId, action: 'core', core_summary: parts.slice(2).join('|').trim().slice(0, 120) })
156
+ continue
157
+ }
158
+ if (action === 'why') {
159
+ const kind = (parts[2] ?? '').trim().toUpperCase()
160
+ const reason = parts.slice(3).join('|').replace(/\s+/g, ' ').trim().slice(0, 240)
161
+ if ((kind === 'A' || kind === 'B') && reason) {
162
+ support.set(entryId, { kind, reason })
163
+ }
164
+ continue
165
+ }
166
+ const allowed = STATUS_ALLOWED_VERBS[status]
167
+ if (!allowed || !allowed.has(action)) {
168
+ __mixdogMemoryLog(`[cycle2] verb rejected: id=${entryId} status=${status} verb=${action}\n`)
169
+ rejected.add(entryId)
170
+ continue
171
+ }
172
+ if (action === 'update') {
173
+ actions.push({
174
+ entry_id: entryId, action,
175
+ element: (parts[2] ?? '').trim(),
176
+ summary: parts.slice(3).join('|').trim(),
177
+ })
178
+ } else if (action === 'merge') {
179
+ const targetId = resolveId(parts[2])
180
+ const sourceIds = [...new Set((parts[3] ?? '').split(',').map(s => resolveId(s)).filter(Number.isFinite))]
181
+ if (!Number.isFinite(targetId) || sourceIds.length === 0) {
182
+ __mixdogMemoryLog(`[cycle2] merge rejected: id=${entryId} invalid target/sources\n`)
183
+ rejected.add(entryId)
184
+ continue
185
+ }
186
+ if (targetId !== entryId && !sourceIds.includes(entryId)) {
187
+ __mixdogMemoryLog(
188
+ `[cycle2] merge rejected: id=${entryId} must be target or listed source (target=${targetId} sources=${sourceIds.join(',')})\n`,
189
+ )
190
+ rejected.add(entryId)
191
+ continue
192
+ }
193
+ actions.push({
194
+ entry_id: entryId, action,
195
+ target_id: targetId,
196
+ source_ids: sourceIds,
197
+ element: (parts[4] ?? '').trim(),
198
+ summary: parts.slice(5).join('|').trim(),
199
+ })
200
+ } else {
201
+ actions.push({ entry_id: entryId, action })
202
+ }
203
+ }
204
+ if (!sawValid && rejected.size === 0) return null
205
+ return { actions, rejected, support }
206
+ }
207
+
208
+ // Batch CTE UPDATE for status-only verdicts (active/archived from pending or active rows).
209
+ // Trigger handles score recompute automatically — no app-side score writes.
210
+ async function applyBatchStatusVerdicts(db, batch, nowMs) {
211
+ if (!batch || batch.length === 0) return { promoted: 0, archived: 0 }
212
+ const valueRows = batch.map((item, i) => {
213
+ const base = i * 3
214
+ return `($${base + 1}::bigint, $${base + 2}::text, $${base + 3}::boolean)`
215
+ })
216
+ const params = []
217
+ for (const item of batch) {
218
+ params.push(item.entry_id, item.new_status, item.was_pending)
219
+ }
220
+ params.push(nowMs)
221
+ const lastParam = `$${params.length}`
222
+ const res = await db.query(
223
+ `WITH actions(entry_id, new_status, was_pending) AS (
224
+ VALUES ${valueRows.join(', ')}
225
+ )
226
+ UPDATE entries
227
+ SET status = a.new_status::entry_status,
228
+ last_seen_at = ${lastParam},
229
+ promoted_at = CASE
230
+ WHEN a.was_pending AND a.new_status = 'active' THEN ${lastParam}
231
+ ELSE promoted_at
232
+ END
233
+ FROM actions a
234
+ WHERE entries.id = a.entry_id AND entries.is_root = 1
235
+ RETURNING entries.id, entries.status, a.was_pending, a.new_status`,
236
+ params,
237
+ )
238
+ let promoted = 0
239
+ let archived = 0
240
+ for (const r of (res.rows ?? [])) {
241
+ if (r.was_pending && r.new_status === 'active') promoted += 1
242
+ else if (r.new_status === 'archived') archived += 1
243
+ }
244
+ return { promoted, archived }
245
+ }
246
+
247
+ // Generic status update for archived/active terminal transitions.
248
+ export async function applySimpleStatus(db, entryId, nextStatus) {
249
+ const res = await db.query(
250
+ `UPDATE entries SET status = $1 WHERE id = $2 AND is_root = 1`,
251
+ [nextStatus, entryId],
252
+ )
253
+ return Number(res.rowCount ?? res.affectedRows ?? 0) > 0
254
+ }
255
+
256
+ export async function applyUpdate(db, entryId, element, summary, options = {}) {
257
+ const setClauses = []
258
+ const params = []
259
+ let paramIdx = 1
260
+ const newElement = (typeof element === 'string' && element.trim()) ? element.trim() : null
261
+ const newSummary = (typeof summary === 'string' && summary.trim()) ? summary.trim() : null
262
+ if (newElement) {
263
+ setClauses.push(`element = $${paramIdx++}`); params.push(newElement)
264
+ }
265
+ if (newSummary) {
266
+ setClauses.push(`summary = $${paramIdx++}`); params.push(newSummary)
267
+ setClauses.push('summary_hash = NULL')
268
+ }
269
+ if (setClauses.length === 0) return false
270
+ params.push(entryId)
271
+ const res = await db.query(
272
+ `UPDATE entries SET ${setClauses.join(', ')} WHERE id = $${paramIdx} AND is_root = 1`,
273
+ params,
274
+ )
275
+ if (Number(res.rowCount ?? res.affectedRows ?? 0) === 0) return false
276
+ await syncRootEmbedding(db, entryId, options)
277
+ return true
278
+ }
279
+
280
+ export async function applyMerge(db, targetId, sourceIds, options = {}) {
281
+ const signal = options?.signal
282
+ throwIfAborted(signal)
283
+ if (!Number.isFinite(targetId)) return 0
284
+ const targetRes = await db.query(
285
+ `SELECT id, project_id FROM entries WHERE id = $1 AND is_root = 1`,
286
+ [targetId],
287
+ )
288
+ throwIfAborted(signal)
289
+ const target = targetRes.rows[0]
290
+ if (!target) return 0
291
+ let moved = 0
292
+ for (const src of sourceIds) {
293
+ throwIfAborted(signal)
294
+ const sid = Number(src)
295
+ if (!Number.isFinite(sid) || sid === targetId) continue
296
+ const srcRes = await db.query(
297
+ `SELECT id, project_id, status FROM entries WHERE id = $1 AND is_root = 1`,
298
+ [sid],
299
+ )
300
+ throwIfAborted(signal)
301
+ const srcRow = srcRes.rows[0]
302
+ if (!srcRow) continue
303
+ if (target.project_id !== srcRow.project_id) {
304
+ __mixdogMemoryLog(
305
+ `[cycle2] merge rejected: cross-pool (target=${targetId} project_id=${target.project_id ?? 'COMMON'} src=${sid} project_id=${srcRow.project_id ?? 'COMMON'})\n`,
306
+ )
307
+ continue
308
+ }
309
+ try {
310
+ // One source merge is the mutation unit: DB reassignment/archive plus
311
+ // embedding cleanup. The next abort checkpoint is before the next source.
312
+ await db.transaction(async (tx) => {
313
+ await tx.query(
314
+ `UPDATE entries SET chunk_root = $1, project_id = $2 WHERE chunk_root = $3 AND id != $4 AND is_root = 0`,
315
+ [targetId, target.project_id, sid, sid],
316
+ )
317
+ await tx.query(
318
+ `UPDATE entries SET status = 'archived' WHERE id = $1 AND is_root = 1`,
319
+ [sid],
320
+ )
321
+ })
322
+ await deleteRootEmbedding(db, sid)
323
+ moved += 1
324
+ } catch (err) {
325
+ __mixdogMemoryLog(`[cycle2] merge failed (target=${targetId} src=${sid}): ${err.message}\n`)
326
+ }
327
+ }
328
+ return moved
329
+ }
330
+
331
+ // ─── phase_merge: cosine-similarity dedup pass ───────────────────────────────
332
+
333
+ function _pickKeeper(a, b) {
334
+ if ((a.score ?? 0) !== (b.score ?? 0)) return (a.score ?? 0) > (b.score ?? 0) ? a : b
335
+ if ((a.last_seen_at ?? 0) !== (b.last_seen_at ?? 0)) return (a.last_seen_at ?? 0) > (b.last_seen_at ?? 0) ? a : b
336
+ return a.id < b.id ? a : b
337
+ }
338
+
339
+ async function _llmJudgePair(summaryA, summaryB, siblingContext = [], options = {}) {
340
+ const signal = options?.signal
341
+ throwIfAborted(signal)
342
+ const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callBridgeLlm
343
+ const siblings = Array.isArray(siblingContext) && siblingContext.length > 0
344
+ ? `\n\nSibling near-matches (recall context only — do not absorb these into the verdict):\n${siblingContext.slice(0, 5).map((p, i) => `${i + 1}. ${String(p.a?.summary ?? '')} ↔ ${String(p.b?.summary ?? '')}`).join('\n')}`
345
+ : ''
346
+ const prompt =
347
+ `Two memory entries below. Are they restating the same principle? Reply ONE WORD: merge or distinct.\n\nA: ${summaryA}\nB: ${summaryB}${siblings}`
348
+ try {
349
+ const raw = await llmCall({
350
+ role: 'cycle2-agent',
351
+ taskType: 'maintenance',
352
+ mode: 'cycle2-phase_merge_judge',
353
+ preset: 'HAIKU',
354
+ timeout: 30000,
355
+ cwd: null,
356
+ }, prompt)
357
+ throwIfAborted(signal)
358
+ return String(raw ?? '').trim().toLowerCase().startsWith('merge')
359
+ } catch (err) {
360
+ if (signal?.aborted) throw signal.reason ?? err
361
+ __mixdogMemoryLog(`[cycle2] phase_merge llm-judge error: ${err.message}\n`)
362
+ return false
363
+ }
364
+ }
365
+
366
+ export async function runPhaseMerge(db, options = {}) {
367
+ const signal = options?.signal
368
+ throwIfAborted(signal)
369
+ // PG-side lateral nearest-neighbor via HNSW index — replaces JS O(n²) double loop.
370
+ const pairRes = await db.query(
371
+ `WITH active AS (
372
+ SELECT id, category, summary, score, last_seen_at, status, embedding, project_id
373
+ FROM entries
374
+ WHERE is_root = 1 AND status = 'active' AND embedding IS NOT NULL
375
+ )
376
+ SELECT a.id AS a_id, a.category AS a_category, a.summary AS a_summary, a.score AS a_score, a.last_seen_at AS a_last_seen_at, a.status AS a_status,
377
+ b.id AS b_id, b.category AS b_category, b.summary AS b_summary, b.score AS b_score, b.last_seen_at AS b_last_seen_at, b.status AS b_status,
378
+ 1 - (a.embedding <=> b.embedding)::float8 AS sim
379
+ FROM active a
380
+ CROSS JOIN LATERAL (
381
+ SELECT id, category, summary, score, last_seen_at, status, embedding
382
+ FROM active inner_b
383
+ WHERE inner_b.id != a.id AND inner_b.category = a.category
384
+ AND inner_b.project_id IS NOT DISTINCT FROM a.project_id
385
+ ORDER BY inner_b.embedding <=> a.embedding
386
+ LIMIT 8
387
+ ) b
388
+ WHERE a.id < b.id
389
+ AND 1 - (a.embedding <=> b.embedding) >= $1
390
+ ORDER BY sim DESC`,
391
+ [TIER2_LOW],
392
+ )
393
+ throwIfAborted(signal)
394
+
395
+ const tier1Pairs = []
396
+ const tier2Pairs = []
397
+ for (const row of pairRes.rows) {
398
+ throwIfAborted(signal)
399
+ const a = { id: row.a_id, category: row.a_category, summary: row.a_summary, score: row.a_score, last_seen_at: row.a_last_seen_at, status: row.a_status }
400
+ const b = { id: row.b_id, category: row.b_category, summary: row.b_summary, score: row.b_score, last_seen_at: row.b_last_seen_at, status: row.b_status }
401
+ if (row.sim >= TIER1_THRESHOLD) tier1Pairs.push({ a, b, sim: row.sim })
402
+ else tier2Pairs.push({ a, b, sim: row.sim })
403
+ }
404
+
405
+ // No active/active similarity pairs is NOT a reason to skip the
406
+ // core_entries overlap sweep below — that pass archives active entries
407
+ // that restate a user-curated core row and is independent of intra-
408
+ // entry pairing. Falling through with merged=0 keeps the cross-table
409
+ // sweep running and the per-phase log shape intact.
410
+ let merged = 0
411
+ let llmCalls = 0
412
+ const mergedIds = new Set()
413
+
414
+ const doMerge = async (a, b, sim) => {
415
+ throwIfAborted(signal)
416
+ if (mergedIds.has(a.id) || mergedIds.has(b.id)) return
417
+ const keeper = _pickKeeper(a, b)
418
+ const loser = keeper.id === a.id ? b : a
419
+ const moved = await applyMerge(db, keeper.id, [loser.id], { signal })
420
+ if (moved > 0) {
421
+ merged += moved
422
+ mergedIds.add(loser.id)
423
+ __mixdogMemoryLog(
424
+ `[cycle2] phase_merge merged id=${loser.id} -> keeper=${keeper.id} category=${keeper.category} sim=${typeof sim === 'number' ? sim.toFixed(3) : '?'}\n`,
425
+ )
426
+ }
427
+ }
428
+
429
+ // Only tier1 pairs enter the LLM judge. Tier2 pairs (0.65 ≤ sim < 0.78)
430
+ // are recall context only — passed as sibling examples to the judge, never
431
+ // as judge input themselves, and never archived here.
432
+ for (const pair of tier1Pairs) {
433
+ throwIfAborted(signal)
434
+ if (llmCalls >= LLM_JUDGE_CAP) break
435
+ if (mergedIds.has(pair.a.id) || mergedIds.has(pair.b.id)) continue
436
+ llmCalls++
437
+ const shouldMerge = await _llmJudgePair(
438
+ String(pair.a.summary ?? ''),
439
+ String(pair.b.summary ?? ''),
440
+ tier2Pairs,
441
+ { signal },
442
+ )
443
+ throwIfAborted(signal)
444
+ if (shouldMerge) await doMerge(pair.a, pair.b, pair.sim)
445
+ }
446
+
447
+ // Cross-table sweep: surface every active entry whose embedding sits near
448
+ // a user-curated core_entries row (sim ≥ TIER2_LOW for broad recall) and
449
+ // ask the LLM whether the entry is a restatement of that user rule. Only
450
+ // the LLM verdict moves the entry to archived — embedding sim alone is
451
+ // never authoritative. Project-scoped core only matches the same pool;
452
+ // COMMON core is global and may absorb duplicate generated project memory.
453
+ throwIfAborted(signal)
454
+ const coreOverlapRes = await db.query(
455
+ `WITH active_e AS (
456
+ SELECT id, project_id, summary, embedding
457
+ FROM entries
458
+ WHERE is_root = 1 AND status = 'active' AND embedding IS NOT NULL
459
+ )
460
+ SELECT e.id AS entry_id, e.summary AS entry_summary, c.core_id, c.core_summary, c.sim
461
+ FROM active_e e
462
+ CROSS JOIN LATERAL (
463
+ SELECT inner_c.id AS core_id, inner_c.summary AS core_summary,
464
+ 1 - (e.embedding <=> inner_c.embedding)::float8 AS sim
465
+ FROM core_entries inner_c
466
+ WHERE inner_c.embedding IS NOT NULL
467
+ AND (inner_c.project_id IS NULL OR inner_c.project_id IS NOT DISTINCT FROM e.project_id)
468
+ ORDER BY
469
+ CASE WHEN inner_c.project_id IS NOT DISTINCT FROM e.project_id THEN 0 ELSE 1 END,
470
+ inner_c.embedding <=> e.embedding
471
+ LIMIT 1
472
+ ) c
473
+ WHERE c.sim >= $1`,
474
+ [TIER1_THRESHOLD],
475
+ )
476
+ throwIfAborted(signal)
477
+ let coreOverlap = 0
478
+ for (const row of coreOverlapRes.rows) {
479
+ throwIfAborted(signal)
480
+ if (llmCalls >= LLM_JUDGE_CAP) break
481
+ llmCalls++
482
+ const verdictMerge = await _llmJudgePair(
483
+ String(row.entry_summary ?? ''),
484
+ String(row.core_summary ?? ''),
485
+ [],
486
+ { signal },
487
+ )
488
+ throwIfAborted(signal)
489
+ if (!verdictMerge) continue
490
+ // Archiving one overlap and deleting its embedding is one mutation unit;
491
+ // cancellation resumes at the next row boundary.
492
+ const r = await db.query(
493
+ `UPDATE entries SET status = 'archived' WHERE id = $1 AND is_root = 1 AND status = 'active'`,
494
+ [Number(row.entry_id)],
495
+ )
496
+ if (Number(r.rowCount ?? r.affectedRows ?? 0) > 0) {
497
+ coreOverlap++
498
+ await deleteRootEmbedding(db, Number(row.entry_id))
499
+ }
500
+ }
501
+ throwIfAborted(signal)
502
+ if (coreOverlap > 0) {
503
+ __mixdogMemoryLog(
504
+ `[cycle2] phase_merge core_overlap archived=${coreOverlap} (LLM-judged restatements of user-curated core_entries)\n`,
505
+ )
506
+ }
507
+
508
+ __mixdogMemoryLog(
509
+ `[cycle2] phase_merge tier1_pairs=${tier1Pairs.length} tier2_pairs=${tier2Pairs.length}` +
510
+ ` llm_calls=${llmCalls} merged=${merged} core_overlap=${coreOverlap}\n`,
511
+ )
512
+
513
+ return { merged, llm_calls: llmCalls, tier1_pairs: tier1Pairs.length, tier2_pairs: tier2Pairs.length, core_overlap: coreOverlap }
514
+ }
515
+
516
+ // ─── Current rules digest cache ──────────────────────────────────────────────
517
+
518
+ let _currentRulesDigest = null
519
+ let _currentRulesDigestTs = 0
520
+ export function loadCurrentRulesDigest() {
521
+ const now = Date.now()
522
+ if (_currentRulesDigest && now - _currentRulesDigestTs < 60_000) return _currentRulesDigest
523
+ const sources = [
524
+ join(resourceDir(), 'rules', 'shared', '00-language.md'),
525
+ join(resourceDir(), 'rules', 'shared', '01-general.md'),
526
+ join(resourceDir(), 'rules', 'shared', '01-tool.md'),
527
+ join(resourceDir(), 'rules', 'shared', '04-memory.md'),
528
+ join(resourceDir(), 'rules', 'shared', '06-team.md'),
529
+ join(resourceDir(), 'rules', 'shared', '07-workflow.md'),
530
+ ]
531
+ const parts = []
532
+ for (const p of sources) {
533
+ try {
534
+ if (!existsSync(p)) continue
535
+ const txt = readFileSync(p, 'utf8').trim()
536
+ if (txt) parts.push(`# Source: ${p}\n${txt}`)
537
+ } catch {}
538
+ }
539
+ const joined = parts.join('\n\n---\n\n')
540
+ const CAP = 40_000
541
+ _currentRulesDigest = joined.length > CAP ? joined.slice(0, CAP) + '\n…[truncated]' : joined
542
+ _currentRulesDigestTs = now
543
+ return _currentRulesDigest
544
+ }
545
+
546
+ function uniqueIds(values) {
547
+ return [...new Set(values
548
+ .map(id => Number(id))
549
+ .filter(id => Number.isFinite(id)))]
550
+ }
551
+
552
+ function validateUnifiedGate(parsed, statusById) {
553
+ const actions = Array.isArray(parsed?.actions) ? parsed.actions : []
554
+ const primary = actions.filter(a => a?.action !== 'core')
555
+ const verdictCounts = new Map()
556
+ for (const action of primary) {
557
+ const id = Number(action?.entry_id)
558
+ if (!Number.isFinite(id)) continue
559
+ verdictCounts.set(id, (verdictCounts.get(id) || 0) + 1)
560
+ }
561
+ const expectedIds = [...statusById.keys()]
562
+ const missingVerdictIds = expectedIds.filter(id => !verdictCounts.has(id))
563
+ const duplicateVerdictIds = [...verdictCounts.entries()]
564
+ .filter(([, count]) => count > 1)
565
+ .map(([id]) => id)
566
+ const support = parsed?.support instanceof Map ? parsed.support : new Map()
567
+ const coreIds = new Set(actions
568
+ .filter(a => a?.action === 'core')
569
+ .map(a => Number(a.entry_id))
570
+ .filter(id => Number.isFinite(id)))
571
+ const missingSupportIds = []
572
+ const missingCoreIds = []
573
+ for (const action of primary) {
574
+ if (!NON_ARCHIVE_VERBS.has(action?.action)) continue
575
+ const id = Number(action.entry_id)
576
+ if (!Number.isFinite(id)) continue
577
+ const coreId = action.action === 'merge' && Number.isFinite(Number(action.target_id))
578
+ ? Number(action.target_id)
579
+ : id
580
+ const hasSupport = support.has(id) || (action.action === 'merge' && support.has(coreId))
581
+ if (!hasSupport) missingSupportIds.push(id)
582
+ if (!coreIds.has(coreId)) missingCoreIds.push(id)
583
+ }
584
+ return {
585
+ missingVerdictIds: uniqueIds(missingVerdictIds),
586
+ duplicateVerdictIds: uniqueIds(duplicateVerdictIds),
587
+ missingSupportIds: uniqueIds(missingSupportIds),
588
+ missingCoreIds: uniqueIds(missingCoreIds),
589
+ }
590
+ }
591
+
592
+ function gateQualitySummary(quality) {
593
+ const parts = []
594
+ if (quality?.missingVerdictIds?.length) parts.push(`missing verdict ids=${quality.missingVerdictIds.join(',')}`)
595
+ if (quality?.duplicateVerdictIds?.length) parts.push(`duplicate verdict ids=${quality.duplicateVerdictIds.join(',')}`)
596
+ if (quality?.missingSupportIds?.length) parts.push(`missing why ids=${quality.missingSupportIds.join(',')}`)
597
+ if (quality?.missingCoreIds?.length) parts.push(`missing core ids=${quality.missingCoreIds.join(',')}`)
598
+ return parts.join('; ')
599
+ }
600
+
601
+ function stripUnsupportedPromotions(parsed, unsupportedIds) {
602
+ const ids = new Set(uniqueIds(unsupportedIds))
603
+ if (ids.size === 0) return parsed
604
+ const rejected = new Set(parsed?.rejected || [])
605
+ for (const id of ids) rejected.add(id)
606
+ const actions = (parsed?.actions || []).filter(a => {
607
+ if (a?.action === 'core') return true
608
+ return !ids.has(Number(a?.entry_id))
609
+ })
610
+ return { ...parsed, actions, rejected }
611
+ }
612
+
613
+ function requiredCoreIdForAction(action) {
614
+ if (action?.action === 'merge' && Number.isFinite(Number(action.target_id))) {
615
+ return Number(action.target_id)
616
+ }
617
+ return Number(action?.entry_id)
618
+ }
619
+
620
+ // ─── Unified gate ────────────────────────────────────────────────────────────
621
+
622
+ // Single LLM pass over rows whose status is in {pending, active}.
623
+ // Returns { actions, rejected, parseOk } following parseUnifiedFormat shape.
624
+ export async function runUnifiedGate(db, rows, activeContext, config = {}, options = {}) {
625
+ const signal = options?.signal
626
+ throwIfAborted(signal)
627
+ if (!rows || rows.length === 0) return { actions: [], rejected: new Set(), parseOk: true }
628
+ const promptPath = join(resourceDir(), 'defaults', 'memory-promote-prompt.md')
629
+ if (!existsSync(promptPath)) {
630
+ throw new Error(`runCycle2: prompt file missing at ${promptPath}`)
631
+ }
632
+ const template = readFileSync(promptPath, 'utf8')
633
+ const userCoreRows = options.dataDir ? await listCore(options.dataDir, '*').catch(() => []) : []
634
+ throwIfAborted(signal)
635
+ const sharedPidMap = buildPidMap([activeContext ?? [], rows ?? [], userCoreRows ?? []])
636
+ const rulesDigest = loadCurrentRulesDigest() || '(no current rules digest available)'
637
+ const activeCount = activeContext?.length ?? 0
638
+ const activeCap = options.activeCap ?? CYCLE2_ACTIVE_TARGET_CAP
639
+
640
+ const prompt = template
641
+ .replace('{{CURRENT_RULES}}', rulesDigest)
642
+ .replace('{{USER_CORE}}', formatUserCoreForPrompt(userCoreRows, sharedPidMap))
643
+ .replace('{{CORE_MEMORY}}', formatEntriesForPromotePrompt(activeContext, sharedPidMap))
644
+ .replace('{{ITEMS}}', formatEntriesForPromotePrompt(rows, sharedPidMap, { numbered: true }))
645
+ .replace('{{ACTIVE_COUNT}}', String(activeCount))
646
+ .replace('{{ACTIVE_CAP}}', String(activeCap))
647
+
648
+ const preset = options.preset || resolveMaintenancePreset('memory')
649
+ const timeout = Number(config?.cycle2?.timeout ?? 600000)
650
+ const mode = 'cycle2-unified'
651
+
652
+ const previewRaw = (raw) => String(raw ?? '').replace(/\s+/g, ' ').slice(0, 200)
653
+ const callOnce = async (extraTag) => {
654
+ throwIfAborted(signal)
655
+ const p = extraTag ? `${prompt}\n\n[retry:${extraTag}]` : prompt
656
+ const raw = await invokeLlm(p, mode, preset, timeout, options.callLlm)
657
+ throwIfAborted(signal)
658
+ return raw
659
+ }
660
+
661
+ const statusById = new Map(rows.map(r => [Number(r.id), String(r.status)]))
662
+ // Ordinal → batch-id map, keyed by 1-based prompt order (the same order the
663
+ // numbered Entries block uses). The gate may echo either the real 5-digit
664
+ // batch id or the row ordinal 1..N; the parser resolves both. The two domains
665
+ // MUST be disjoint or an ordinal could shadow a real id (ids are 5-digit,
666
+ // ordinals are <= rows.length, so disjointness always holds in practice).
667
+ // On a violation a row-number line is indistinguishable from an exact-id
668
+ // line, so no safe interpretation exists — skip this batch (gate failure)
669
+ // rather than risk applying a verdict to the wrong entry. The cycle itself
670
+ // proceeds; the batch re-queues for a later run.
671
+ const ordinalToId = new Map(rows.map((r, i) => [i + 1, Number(r.id)]))
672
+ const minBatchId = Math.min(...[...statusById.keys()])
673
+ if (Number.isFinite(minBatchId) && minBatchId <= rows.length) {
674
+ __mixdogMemoryLog(`[cycle2] batch id ${minBatchId} collides with ordinal range 1..${rows.length} — skipping batch (no safe id resolution)\n`)
675
+ return { actions: null, rejected: new Set(), parseOk: false }
676
+ }
677
+
678
+ __mixdogMemoryLog(`[cycle2-diag] unified prompt=${prompt.length} bytes; rows=${rows.length}\n`)
679
+
680
+ let raw
681
+ try {
682
+ raw = await callOnce(null)
683
+ } catch (err) {
684
+ if (signal?.aborted) throw signal.reason ?? err
685
+ __mixdogMemoryLog(`[cycle2] unified LLM error: ${err.message}\n`)
686
+ return { actions: null, rejected: new Set(), parseOk: false }
687
+ }
688
+ throwIfAborted(signal)
689
+ __mixdogMemoryLog(`[cycle2-diag] unified raw (first 1500): ${String(raw ?? '').replace(/\n/g, '⏎').slice(0, 1500)}\n`)
690
+
691
+ let parsed = parseUnifiedFormat(raw, statusById, ordinalToId)
692
+ let quality = parsed ? validateUnifiedGate(parsed, statusById) : null
693
+ const qualityIssue = () => gateQualitySummary(quality)
694
+ if (!parsed || qualityIssue()) {
695
+ throwIfAborted(signal)
696
+ const issue = parsed ? qualityIssue() : `unparseable (${previewRaw(raw)})`
697
+ __mixdogMemoryLog(`[cycle2] unified quality retry: ${issue}\n`)
698
+ // Preserve the first pass before retrying. A retry fired for a mere quality
699
+ // issue (e.g. a few missing verdicts) must not throw away an otherwise-valid
700
+ // first-pass parse if the retry comes back unparseable.
701
+ const firstParsed = parsed
702
+ const firstQuality = quality
703
+ try {
704
+ const retryTag = parsed
705
+ ? 'complete-verdicts-with-why-and-core-lines'
706
+ : 'first-field-must-be-the-listed-row-number'
707
+ const raw2 = await callOnce(retryTag)
708
+ const retryParsed = parseUnifiedFormat(raw2, statusById, ordinalToId)
709
+ if (retryParsed) {
710
+ parsed = retryParsed
711
+ quality = validateUnifiedGate(retryParsed, statusById)
712
+ } else if (firstParsed) {
713
+ __mixdogMemoryLog(`[cycle2] unparseable after retry — falling back to first-pass parse (${previewRaw(raw2)})\n`)
714
+ parsed = firstParsed
715
+ quality = firstQuality
716
+ } else {
717
+ __mixdogMemoryLog(`[cycle2] unparseable after retry — skipping batch (${previewRaw(raw2)})\n`)
718
+ return { actions: null, rejected: new Set(), parseOk: false }
719
+ }
720
+ } catch (err) {
721
+ if (signal?.aborted) throw signal.reason ?? err
722
+ if (firstParsed) {
723
+ __mixdogMemoryLog(`[cycle2] retry LLM error: ${err.message} — falling back to first-pass parse\n`)
724
+ parsed = firstParsed
725
+ quality = firstQuality
726
+ } else {
727
+ __mixdogMemoryLog(`[cycle2] retry LLM error: ${err.message}\n`)
728
+ return { actions: null, rejected: new Set(), parseOk: false }
729
+ }
730
+ }
731
+ }
732
+ const finalIssue = gateQualitySummary(quality)
733
+ // duplicateVerdictIds are genuinely ambiguous (the same row got two conflicting
734
+ // verbs) — keep the full-skip. missingVerdictIds, by contrast, used to skip the
735
+ // WHOLE batch, so a handful of persistently-missing poison rows could livelock
736
+ // the gate. Partial-apply instead: keep the valid verdicts we did receive, just
737
+ // log the missing ids and leave those rows for a later run.
738
+ if (quality?.duplicateVerdictIds?.length) {
739
+ __mixdogMemoryLog(`[cycle2] duplicate verdict coverage after retry — skipping batch (${finalIssue})\n`)
740
+ return { actions: null, rejected: new Set(), parseOk: false }
741
+ }
742
+ if (quality?.missingVerdictIds?.length) {
743
+ __mixdogMemoryLog(`[cycle2] missing verdicts after retry — partial apply, leaving ids=${quality.missingVerdictIds.join(',')} for a later run (${finalIssue})\n`)
744
+ }
745
+ // A response made up solely of why/core lines parses "ok" yet carries zero
746
+ // primary (status-verb) verdicts. Without this guard parseOk stays true and
747
+ // the caller treats the batch as a clean no-op, masking the coverage failure
748
+ // and marking the rows reviewed. Fail the parse so the rows are re-queued.
749
+ const primaryCount = (parsed.actions || []).filter(a => a?.action !== 'core').length
750
+ if (rows.length > 0 && primaryCount === 0) {
751
+ __mixdogMemoryLog(`[cycle2] gate produced zero primary verdicts for ${rows.length} rows — failing parse\n`)
752
+ return { actions: null, rejected: new Set(), parseOk: false, missingIds: [...statusById.keys()] }
753
+ }
754
+ const incompletePromotionIds = uniqueIds([
755
+ ...(quality?.missingSupportIds || []),
756
+ ...(quality?.missingCoreIds || []),
757
+ ])
758
+ if (incompletePromotionIds.length > 0) {
759
+ __mixdogMemoryLog(`[cycle2] incomplete non-archive verdicts rejected after retry ids=${incompletePromotionIds.join(',')} (${finalIssue})\n`)
760
+ parsed = stripUnsupportedPromotions(parsed, incompletePromotionIds)
761
+ }
762
+ return {
763
+ actions: parsed.actions,
764
+ rejected: parsed.rejected,
765
+ parseOk: true,
766
+ missingIds: quality?.missingVerdictIds || [],
767
+ }
768
+ }
769
+
770
+ // ─── Sonnet cascade ──────────────────────────────────────────────────────────
771
+
772
+ // Sonnet re-judge over first-pass keep verdicts. Sonnet sees rules + summary
773
+ // and returns binary keep/drop. Failures fail-open (preserve first-pass).
774
+ async function sonnetCascade(candidates, rulesDigest, options = {}) {
775
+ const signal = options?.signal
776
+ throwIfAborted(signal)
777
+ if (!candidates || candidates.length === 0) return new Map()
778
+ const lines = candidates.map(c =>
779
+ `id:${c.id} status:${c.status} verb:${c.verb} cat:${c.category} el:${c.element} sm:${String(c.summary || '').slice(0, 200)}${c.core ? ` core:${String(c.core).slice(0, 200)}` : ''}`,
780
+ ).join('\n')
781
+ const prompt = [
782
+ `Final gate over first-pass keep verdicts.`,
783
+ `Keep a candidate ONLY if it lands in one of three layers: L1 relationship/communication`,
784
+ `(user identity, address form, reply-style preferences, disliked patterns); L2 behavior rules`,
785
+ `(principles the user corrected/insisted on, hard safety boundaries, quality bars); or L3 current`,
786
+ `map (one-line project-landscape summaries, live long-running goals, environment anchors documented`,
787
+ `nowhere else). For a past decision/failure, keep only the one-line lesson that still constrains`,
788
+ `behavior, else drop. DROP anything whose source of truth is code, rules files, or skill docs, plus`,
789
+ `implementation specs, code-internal constants, measurements, resolved-bug stories, status snapshots,`,
790
+ `and duplicates of source-of-truth rules.`,
791
+ `When a candidate has a core: field, judge THAT extracted one-line lesson (the entry will live as`,
792
+ `that line), not the raw narrative in el:/sm:.`,
793
+ ``,
794
+ `Source-of-truth rules (excerpt — DO NOT duplicate in memory):`,
795
+ String(rulesDigest || '').slice(0, 4000),
796
+ ``,
797
+ `Candidates:`,
798
+ lines,
799
+ ``,
800
+ `Reply one line per id: "<id>|keep" to retain, "<id>|drop" to reject.`,
801
+ `NO prose, NO preamble, NO meta-commentary. First character must be a digit.`,
802
+ ].join('\n')
803
+
804
+ // Hardcoded — resolveMaintenancePreset falls back to first preset (HAIKU)
805
+ // when no binding exists, which would defeat the cascade. SONNET HIGH
806
+ // matches the worker pool's default preset id from agent-config.
807
+ const preset = options.cascadePreset || 'SONNET HIGH'
808
+ const llmCall = typeof options?.callLlm === 'function' ? options.callLlm : callBridgeLlm
809
+ let raw
810
+ try {
811
+ raw = await llmCall({
812
+ role: 'cycle2-agent',
813
+ taskType: 'maintenance',
814
+ mode: 'cycle2-cascade',
815
+ preset,
816
+ timeout: 600000,
817
+ cwd: null,
818
+ }, prompt)
819
+ } catch (err) {
820
+ if (signal?.aborted) throw signal.reason ?? err
821
+ __mixdogMemoryLog(`[cycle2] cascade error: ${err.message} — fail-open\n`)
822
+ return new Map()
823
+ }
824
+ throwIfAborted(signal)
825
+
826
+ const verdicts = new Map()
827
+ for (const line of String(raw ?? '').split('\n')) {
828
+ throwIfAborted(signal)
829
+ const trimmed = line.trim()
830
+ if (!trimmed) continue
831
+ if (trimmed.startsWith('//') || trimmed.startsWith('#') || trimmed.startsWith('```')) continue
832
+ const parts = trimmed.split('|')
833
+ if (parts.length < 2) continue
834
+ const id = Number(parts[0].trim())
835
+ const v = parts[1].trim().toLowerCase()
836
+ if (Number.isFinite(id) && (v === 'keep' || v === 'drop')) verdicts.set(id, v)
837
+ }
838
+ __mixdogMemoryLog(`[cycle2] cascade evaluated=${candidates.length} drops=${[...verdicts.values()].filter(v => v === 'drop').length}\n`)
839
+ return verdicts
840
+ }
841
+
842
+ // ─── runCycle2 ───────────────────────────────────────────────────────────────
843
+
844
+ const _runCycle2InFlight = new WeakMap()
845
+
846
+ function mergeNestedNumeric(a = {}, b = {}) {
847
+ const out = { ...a, ...b }
848
+ for (const key of new Set([...Object.keys(a || {}), ...Object.keys(b || {})])) {
849
+ out[key] = Number(a?.[key] || 0) + Number(b?.[key] || 0)
850
+ }
851
+ return out
852
+ }
853
+
854
+ function mergeCycle2Results(a, b) {
855
+ if (!a) return b
856
+ if (!b) return a
857
+ return {
858
+ ...a,
859
+ ...b,
860
+ promoted: Number(a.promoted || 0) + Number(b.promoted || 0),
861
+ archived: Number(a.archived || 0) + Number(b.archived || 0),
862
+ merged: Number(a.merged || 0) + Number(b.merged || 0),
863
+ updated: Number(a.updated || 0) + Number(b.updated || 0),
864
+ kept: Number(a.kept || 0) + Number(b.kept || 0),
865
+ rejected_verb: Number(a.rejected_verb || 0) + Number(b.rejected_verb || 0),
866
+ merge_rejected: Number(a.merge_rejected || 0) + Number(b.merge_rejected || 0),
867
+ missing_core_summary: Number(a.missing_core_summary || 0) + Number(b.missing_core_summary || 0),
868
+ core_embedding_backfill: Number(a.core_embedding_backfill || 0) + Number(b.core_embedding_backfill || 0),
869
+ rescore: mergeNestedNumeric(a.rescore, b.rescore),
870
+ phase_merge: mergeNestedNumeric(a.phase_merge, b.phase_merge),
871
+ cascade: mergeNestedNumeric(a.cascade, b.cascade),
872
+ skippedInFlight: false,
873
+ }
874
+ }
875
+
876
+ export async function runCycle2(db, config = {}, options = {}, dataDir = null) {
877
+ const signal = options?.signal
878
+ throwIfAborted(signal)
879
+ const coalescedRetry = options?.coalescedRetry === true
880
+ const retryAttempt = Math.max(0, Number(options?.coalescedRetryAttempt || 0))
881
+ const maxRetries = resolveCoalesceMaxRetries(config, 3)
882
+ const requestSignature = makeCycleRequestSignature('cycle2', config, {
883
+ cascadePreset: options?.cascadePreset,
884
+ concurrency: options?.concurrency,
885
+ })
886
+ const scheduleRetry = () => scheduleCoalescedCycleRetry(
887
+ db,
888
+ 'cycle2',
889
+ () => runCycle2(db, config, { ...options, signal: undefined, coalescedRetry: true, coalescedRetryAttempt: retryAttempt + 1 }, dataDir),
890
+ config,
891
+ requestSignature,
892
+ )
893
+ const partial = {
894
+ promoted: 0, archived: 0, merged: 0, updated: 0, kept: 0, rejected_verb: 0,
895
+ merge_rejected: 0,
896
+ missing_core_summary: 0,
897
+ core_embedding_backfill: 0,
898
+ rescore: { updated: 0 },
899
+ phase_merge: { merged: 0, llm_calls: 0, tier1_pairs: 0, tier2_pairs: 0, core_overlap: 0 },
900
+ cascade: { evaluated: 0, dropped: 0 },
901
+ }
902
+ if (_runCycle2InFlight.has(db)) {
903
+ if (!coalescedRetry) await markCycleRequest(db, 'cycle2', 'in-flight', requestSignature)
904
+ if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
905
+ __mixdogMemoryLog('[cycle2] skipped: already in flight for this db\n')
906
+ return { ok: true, ...partial, skippedInFlight: true }
907
+ }
908
+ const client = await db._pool.connect()
909
+ let gotLock = false
910
+ try {
911
+ throwIfAborted(signal)
912
+ const r = await client.query(`SELECT pg_try_advisory_lock(hashtext($1)) AS got`, ['mixdog.cycle2'])
913
+ gotLock = r.rows[0]?.got === true
914
+ } catch (err) {
915
+ client.release()
916
+ if (signal?.aborted) throw signal.reason ?? err
917
+ __mixdogMemoryLog(`[cycle2] advisory lock query failed: ${err.message}\n`)
918
+ if (!coalescedRetry) await markCycleRequest(db, 'cycle2', 'lock-error', requestSignature)
919
+ return { ok: true, ...partial, skippedInFlight: true }
920
+ }
921
+ if (!gotLock) {
922
+ client.release()
923
+ if (!coalescedRetry) await markCycleRequest(db, 'cycle2', 'advisory-lock', requestSignature)
924
+ if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
925
+ __mixdogMemoryLog('[cycle2] skipped: advisory lock held by another worker\n')
926
+ return { ok: true, ...partial, skippedInFlight: true }
927
+ }
928
+ const _p = (async () => {
929
+ try {
930
+ let result = null
931
+ let coalescedRuns = 0
932
+ let coalescedRequests = 0
933
+ if (coalescedRetry) {
934
+ const pending = await consumeCycleRequests(db, 'cycle2', requestSignature)
935
+ if (pending <= 0) return { ok: true, ...partial, skippedInFlight: false, coalescedRetryNoop: true }
936
+ coalescedRuns += 1
937
+ coalescedRequests += pending
938
+ __mixdogMemoryLog(`[cycle2] retrying coalesced requests=${pending}\n`)
939
+ }
940
+ try {
941
+ result = await _runCycle2Impl(db, config, options, dataDir)
942
+ } catch (err) {
943
+ if (coalescedRetry) {
944
+ await markCycleRequest(db, 'cycle2', 'retry-error', requestSignature)
945
+ if (retryAttempt < maxRetries) scheduleRetry()
946
+ }
947
+ throw err
948
+ }
949
+ const maxDrains = resolveCoalesceMaxDrains(config, 1)
950
+ let drainLoops = 0
951
+ while (drainLoops < maxDrains) {
952
+ throwIfAborted(signal)
953
+ const pending = await consumeCycleRequests(db, 'cycle2', requestSignature)
954
+ if (pending <= 0) break
955
+ drainLoops += 1
956
+ coalescedRuns += 1
957
+ coalescedRequests += pending
958
+ __mixdogMemoryLog(`[cycle2] draining coalesced requests=${pending}\n`)
959
+ try {
960
+ const next = await _runCycle2Impl(db, config, options, dataDir)
961
+ result = mergeCycle2Results(result, next)
962
+ } catch (err) {
963
+ await markCycleRequest(db, 'cycle2', 'drain-error', requestSignature)
964
+ if (!coalescedRetry || retryAttempt < maxRetries) scheduleRetry()
965
+ throw err
966
+ }
967
+ }
968
+ if (coalescedRuns > 0) {
969
+ result = { ...result, coalescedRuns, coalescedRequests }
970
+ }
971
+ const okResult = { ok: true, ...result }
972
+ if (coalescedRetry && !okResult?.coalescedRetryNoop && typeof options?.onCoalescedSuccess === 'function') {
973
+ try { await options.onCoalescedSuccess(okResult) }
974
+ catch (err) { __mixdogMemoryLog(`[cycle2] coalesced success callback failed: ${err?.message || err}\n`) }
975
+ }
976
+ return okResult
977
+ } catch (e) {
978
+ if (signal?.aborted) throw signal.reason ?? e
979
+ return { ok: false, error: e.message, ...partial }
980
+ } finally {
981
+ let releaseErr = null
982
+ try {
983
+ const r = await client.query(`SELECT pg_advisory_unlock(hashtext($1)) AS unlocked`, ['mixdog.cycle2'])
984
+ if (r.rows[0]?.unlocked !== true) releaseErr = new Error('cycle2 advisory unlock returned false')
985
+ } catch (err) {
986
+ releaseErr = err
987
+ }
988
+ client.release(releaseErr || undefined)
989
+ }
990
+ })()
991
+ _runCycle2InFlight.set(db, _p)
992
+ try { return await _p }
993
+ finally { _runCycle2InFlight.delete(db) }
994
+ }
995
+
996
+ async function _runCycle2Impl(db, config = {}, options = {}, dataDir = null) {
997
+ const signal = options?.signal
998
+ throwIfAborted(signal)
999
+ const batchSize = Math.max(1, Number(config.batch_size ?? 50))
1000
+ const activeTargetCap = Number.isFinite(Number(config.active_target_cap))
1001
+ ? Math.max(1, Number(config.active_target_cap))
1002
+ : CYCLE2_ACTIVE_TARGET_CAP
1003
+ const nowMs = Date.now()
1004
+
1005
+ const stats = {
1006
+ promoted: 0, archived: 0, merged: 0,
1007
+ updated: 0, kept: 0, rejected_verb: 0,
1008
+ merge_rejected: 0,
1009
+ missing_core_summary: 0,
1010
+ core_embedding_backfill: 0,
1011
+ rescore: { updated: 0 },
1012
+ phase_merge: { merged: 0, llm_calls: 0, tier1_pairs: 0, tier2_pairs: 0, core_overlap: 0 },
1013
+ cascade: { evaluated: 0, dropped: 0 },
1014
+ }
1015
+
1016
+ if (dataDir) {
1017
+ try {
1018
+ stats.core_embedding_backfill = await backfillCoreEmbeddings(dataDir, { signal })
1019
+ throwIfAborted(signal)
1020
+ } catch (err) {
1021
+ if (signal?.aborted) throw signal.reason ?? err
1022
+ __mixdogMemoryLog(`[cycle2] core embedding backfill failed: ${err.message}\n`)
1023
+ }
1024
+ }
1025
+
1026
+ const activeCountRes = await db.query(
1027
+ `SELECT COUNT(*) AS c FROM entries WHERE is_root = 1 AND status = 'active'`,
1028
+ [],
1029
+ )
1030
+ throwIfAborted(signal)
1031
+ const activeCount = Number(activeCountRes.rows[0]?.c ?? 0)
1032
+ const reviewActiveRows = activeCount > activeTargetCap
1033
+
1034
+ // Rolling active re-review quota. Under cap, the unified selection below
1035
+ // pulls only pending rows, so an already-promoted entry that later drifts
1036
+ // stale or turns out to restate a rule file never gets re-judged — the
1037
+ // over-cap path was historically the ONLY one that re-examined active.
1038
+ // Reserve a bounded slice of batch slots for the stalest active rows so
1039
+ // rule-duplicate / drifted promotions are archived continuously instead of
1040
+ // sitting forever un-rechecked. Bounded count + reviewed_at rotation
1041
+ // prevents eroding the set to zero (the original over-cap-only concern):
1042
+ // only the oldest few are re-judged per cycle, and the gate — shown
1043
+ // {{CURRENT_RULES}} — keeps genuine A/B entries and archives only
1044
+ // restatements. Embedding dedup is skipped on purpose: rule restatements
1045
+ // are often cross-language paraphrases whose cosine never clears the merge
1046
+ // threshold, but the LLM gate catches the semantic overlap.
1047
+ const activeRecheckQuota = reviewActiveRows
1048
+ ? 0
1049
+ : Math.max(0, Math.min(Number(config.active_recheck_quota ?? 8), batchSize - 1))
1050
+ const pendingLimit = batchSize - activeRecheckQuota
1051
+ // Score direction depends on the phase. Under cap we are SEEDING the active
1052
+ // set: evaluate the highest-value pending first so promotion-worthy rows
1053
+ // reach the gate instead of starving behind low-score cycle1 churn. Over cap
1054
+ // we are CONTRACTING: evaluate the lowest-score rows first to shed the
1055
+ // weakest. (The active-recheck slice below stays ASC — demote weakest active
1056
+ // first.)
1057
+ const scoreDir = reviewActiveRows ? 'ASC' : 'DESC'
1058
+
1059
+ // Unified candidate selection. Pending rows (and, when over cap, active
1060
+ // rows) reach the gate here; the reserved active-recheck slice is appended
1061
+ // below. Cleanup of duplicates/stale user-core overlap also runs via
1062
+ // phase_merge / cycle3.
1063
+ const rowsRes = await db.query(`
1064
+ SELECT id, element, category, summary, score, last_seen_at, project_id, status
1065
+ FROM entries
1066
+ WHERE is_root = 1
1067
+ AND (status = 'pending' OR ($2::boolean AND status = 'active'))
1068
+ ORDER BY
1069
+ CASE status WHEN 'pending' THEN 0 WHEN 'active' THEN 1 END ASC,
1070
+ reviewed_at ASC NULLS FIRST,
1071
+ error_count ASC,
1072
+ score ${scoreDir},
1073
+ id ASC
1074
+ LIMIT $1
1075
+ `, [pendingLimit, reviewActiveRows])
1076
+ throwIfAborted(signal)
1077
+ const rows = rowsRes.rows
1078
+
1079
+ // Append the reserved rolling slice of stalest active rows (under-cap only;
1080
+ // the over-cap branch already pulls active broadly). De-duped against the
1081
+ // primary selection so an id never gets two verdicts in one batch.
1082
+ if (activeRecheckQuota > 0 && activeCount > 0) {
1083
+ const seen = new Set(rows.map(r => Number(r.id)))
1084
+ const recheckRes = await db.query(`
1085
+ SELECT id, element, category, summary, score, last_seen_at, project_id, status
1086
+ FROM entries
1087
+ WHERE is_root = 1 AND status = 'active'
1088
+ ORDER BY reviewed_at ASC NULLS FIRST, score ASC, id ASC
1089
+ LIMIT $1
1090
+ `, [activeRecheckQuota])
1091
+ throwIfAborted(signal)
1092
+ for (const r of recheckRes.rows) {
1093
+ if (!seen.has(Number(r.id))) rows.push(r)
1094
+ }
1095
+ }
1096
+
1097
+ // Active snapshot for prompt context (do-not-duplicate reference).
1098
+ const activeContextRes = await db.query(`
1099
+ SELECT id, element, category, summary, score, last_seen_at, project_id, status
1100
+ FROM entries
1101
+ WHERE is_root = 1 AND status = 'active'
1102
+ ORDER BY score DESC, last_seen_at DESC, id ASC
1103
+ LIMIT 100
1104
+ `, [])
1105
+ throwIfAborted(signal)
1106
+ const activeContext = activeContextRes.rows
1107
+
1108
+ const gateResult = rows.length > 0
1109
+ ? await runUnifiedGate(db, rows, activeContext, config, { activeCap: activeTargetCap, preset: options.preset, dataDir, signal, callLlm: options.callLlm })
1110
+ : { actions: [], rejected: new Set(), parseOk: true }
1111
+ throwIfAborted(signal)
1112
+ // Surface a gate parse/coverage failure so the caller can distinguish a
1113
+ // clean no-op run from one where the LLM gate produced nothing usable.
1114
+ if (gateResult.parseOk === false) stats.gate_failed = true
1115
+
1116
+ const sweepCursor = nowMs
1117
+
1118
+ const rowsById = new Map(rows.map(r => [Number(r.id), r]))
1119
+
1120
+ // Cascade pre-pass: pull first-pass keeps (verb 'active') into Sonnet for
1121
+ // re-judge. update/merge/archived skip.
1122
+ const cascadeCandidates = []
1123
+ if (gateResult.actions) {
1124
+ // First-pass proposed core lines: under the pending-row transform the L2
1125
+ // lesson lives only in the core line, so thread it into the cascade.
1126
+ const proposedCoreById = new Map()
1127
+ for (const a of gateResult.actions) {
1128
+ if (a.action !== 'core') continue
1129
+ const id = Number(a.entry_id)
1130
+ const core = String(a.core_summary ?? '').replace(/\s+/g, ' ').trim()
1131
+ if (Number.isFinite(id) && core) proposedCoreById.set(id, core)
1132
+ }
1133
+ for (const a of gateResult.actions) {
1134
+ throwIfAborted(signal)
1135
+ if (a.action !== 'active') continue
1136
+ const row = rowsById.get(Number(a.entry_id))
1137
+ if (!row) continue
1138
+ cascadeCandidates.push({
1139
+ id: row.id, status: row.status, verb: a.action,
1140
+ category: row.category, element: row.element, summary: row.summary,
1141
+ core: proposedCoreById.get(Number(a.entry_id)) || '',
1142
+ })
1143
+ }
1144
+ }
1145
+
1146
+ const rulesDigest = loadCurrentRulesDigest() || ''
1147
+ let cascadeVerdicts = new Map()
1148
+ if (cascadeCandidates.length > 0) {
1149
+ cascadeVerdicts = await sonnetCascade(cascadeCandidates, rulesDigest, { ...options, signal })
1150
+ throwIfAborted(signal)
1151
+ stats.cascade.evaluated = cascadeCandidates.length
1152
+ }
1153
+
1154
+ // Apply actions.
1155
+ if (gateResult.actions) {
1156
+ const reviewedIds = []
1157
+ const rejectedActionIds = []
1158
+ const cascadeDropArchiveIds = []
1159
+ const statusBatch = []
1160
+ const coreSummaryById = new Map()
1161
+ const primaryActions = []
1162
+
1163
+ for (const a of gateResult.actions) {
1164
+ throwIfAborted(signal)
1165
+ if (a.action === 'core') {
1166
+ const id = Number(a.entry_id)
1167
+ const core = String(a.core_summary ?? '').replace(/\s+/g, ' ').trim().slice(0, CORE_SUMMARY_MAX)
1168
+ if (Number.isFinite(id) && core) coreSummaryById.set(id, core)
1169
+ } else {
1170
+ primaryActions.push(a)
1171
+ }
1172
+ }
1173
+
1174
+ const setCoreSummary = async (entryId, explicitSummary) => {
1175
+ const id = Number(entryId)
1176
+ if (!Number.isFinite(id)) return false
1177
+ let core = String(explicitSummary ?? '').replace(/\s+/g, ' ').trim().slice(0, CORE_SUMMARY_MAX)
1178
+ if (!core) return false
1179
+ await db.query(`UPDATE entries SET core_summary = $1 WHERE id = $2 AND is_root = 1`, [core, id])
1180
+ return true
1181
+ }
1182
+
1183
+ for (const a of primaryActions) {
1184
+ throwIfAborted(signal)
1185
+ const id = Number(a.entry_id)
1186
+ if (!Number.isFinite(id)) continue
1187
+ const row = rowsById.get(id)
1188
+ if (!row) continue
1189
+ let accepted = false
1190
+
1191
+ try {
1192
+ const requiresCore = NON_ARCHIVE_VERBS.has(a.action)
1193
+ const coreId = requiredCoreIdForAction(a)
1194
+ const explicitCore = coreSummaryById.get(coreId) || coreSummaryById.get(id)
1195
+ if (requiresCore && !explicitCore) {
1196
+ stats.missing_core_summary += 1
1197
+ rejectedActionIds.push(id)
1198
+ __mixdogMemoryLog(`[cycle2] non-archive action rejected: missing explicit core line id=${id} action=${a.action}\n`)
1199
+ continue
1200
+ }
1201
+
1202
+ // Cascade override: drop a tentatively-kept entry → archive.
1203
+ if (a.action === 'active' && cascadeVerdicts.get(id) === 'drop') {
1204
+ cascadeDropArchiveIds.push(id)
1205
+ accepted = true
1206
+ reviewedIds.push(id)
1207
+ continue
1208
+ }
1209
+
1210
+ if (a.action === 'active') {
1211
+ if (row.status === 'pending') {
1212
+ statusBatch.push({ entry_id: id, new_status: 'active', was_pending: true })
1213
+ } else if (row.status === 'active') {
1214
+ stats.kept += 1
1215
+ }
1216
+ await setCoreSummary(id, explicitCore)
1217
+ accepted = true
1218
+ } else if (a.action === 'archived') {
1219
+ statusBatch.push({ entry_id: id, new_status: 'archived', was_pending: row.status === 'pending' })
1220
+ accepted = true
1221
+ } else if (a.action === 'update') {
1222
+ if (await applyUpdate(db, id, a.element, a.summary, { signal })) stats.updated += 1
1223
+ await setCoreSummary(id, explicitCore)
1224
+ accepted = true
1225
+ } else if (a.action === 'merge') {
1226
+ const sourceIds = Array.isArray(a.source_ids) ? a.source_ids : []
1227
+ const targetId = Number(a.target_id)
1228
+ if (!Number.isFinite(targetId) || sourceIds.length === 0) {
1229
+ stats.merge_rejected += 1
1230
+ rejectedActionIds.push(id)
1231
+ continue
1232
+ }
1233
+ if (targetId !== id && !sourceIds.map(Number).includes(id)) {
1234
+ stats.merge_rejected += 1
1235
+ rejectedActionIds.push(id)
1236
+ __mixdogMemoryLog(
1237
+ `[cycle2] merge rejected during apply: id=${id} target=${targetId} sources=${sourceIds.join(',')}\n`,
1238
+ )
1239
+ continue
1240
+ }
1241
+ // Bounded-erosion invariant: a merge may only consolidate entries
1242
+ // that are themselves candidates in this batch. Otherwise a single
1243
+ // rechecked active row could list source_ids pointing at active
1244
+ // entries outside the batch (e.g. ids drawn from the activeContext
1245
+ // reference list), and applyMerge would archive those too —
1246
+ // un-judged and beyond the rolling-recheck quota. Out-of-batch
1247
+ // target/source ids are rejected; a true duplicate of an existing
1248
+ // active entry is handled by the `archived` verdict instead.
1249
+ if (![targetId, ...sourceIds.map(Number)].every(mid => rowsById.has(mid))) {
1250
+ stats.merge_rejected += 1
1251
+ rejectedActionIds.push(id)
1252
+ __mixdogMemoryLog(
1253
+ `[cycle2] merge rejected: out-of-batch target/source (target=${targetId} sources=${sourceIds.join(',')})\n`,
1254
+ )
1255
+ continue
1256
+ }
1257
+ const moved = await applyMerge(db, targetId, sourceIds, { signal })
1258
+ throwIfAborted(signal)
1259
+ if (moved > 0) {
1260
+ stats.merged += moved
1261
+ if (typeof a.element === 'string' || typeof a.summary === 'string') {
1262
+ try { if (await applyUpdate(db, targetId, a.element, a.summary, { signal })) stats.updated += 1 }
1263
+ catch (err) {
1264
+ if (signal?.aborted) throw signal.reason ?? err
1265
+ __mixdogMemoryLog(`[cycle2] merge target update failed (target=${targetId}): ${err.message}\n`)
1266
+ }
1267
+ }
1268
+ await setCoreSummary(targetId, explicitCore)
1269
+ accepted = true
1270
+ } else {
1271
+ stats.merge_rejected += 1
1272
+ rejectedActionIds.push(id)
1273
+ }
1274
+ }
1275
+ if (accepted) reviewedIds.push(id)
1276
+ } catch (err) {
1277
+ if (signal?.aborted) throw signal.reason ?? err
1278
+ __mixdogMemoryLog(`[cycle2] action error (id=${id}): ${err.message}\n`)
1279
+ }
1280
+ }
1281
+
1282
+ if (statusBatch.length > 0) {
1283
+ // Status verdicts are applied as one SQL batch; checkpoint before the
1284
+ // batch and then again at the next cycle2 unit boundary.
1285
+ throwIfAborted(signal)
1286
+ const batchRes = await applyBatchStatusVerdicts(db, statusBatch, nowMs)
1287
+ stats.promoted += batchRes.promoted
1288
+ stats.archived += batchRes.archived
1289
+ }
1290
+
1291
+ if (cascadeDropArchiveIds.length > 0) {
1292
+ throwIfAborted(signal)
1293
+ const r = await db.query(`UPDATE entries SET status = 'archived' WHERE id = ANY($1::bigint[]) AND is_root = 1`, [cascadeDropArchiveIds])
1294
+ stats.cascade.dropped += Number(r.rowCount ?? r.affectedRows ?? 0)
1295
+ stats.archived += Number(r.rowCount ?? r.affectedRows ?? 0)
1296
+ }
1297
+ if (reviewedIds.length > 0) {
1298
+ throwIfAborted(signal)
1299
+ await db.query(`UPDATE entries SET reviewed_at = $1 WHERE id = ANY($2::bigint[])`, [sweepCursor, reviewedIds])
1300
+ }
1301
+ if (rejectedActionIds.length > 0) {
1302
+ throwIfAborted(signal)
1303
+ await db.query(
1304
+ `UPDATE entries SET error_count = COALESCE(error_count, 0) + 1 WHERE id = ANY($1::bigint[])`,
1305
+ [[...new Set(rejectedActionIds)]],
1306
+ )
1307
+ }
1308
+ } else if (rows.length > 0) {
1309
+ // Parse failure — bump error_count, do not advance reviewed_at.
1310
+ for (const r of rows) {
1311
+ throwIfAborted(signal)
1312
+ try {
1313
+ await db.query(
1314
+ `UPDATE entries SET error_count = COALESCE(error_count, 0) + 1 WHERE id = $1`,
1315
+ [r.id],
1316
+ )
1317
+ } catch {}
1318
+ }
1319
+ }
1320
+
1321
+ // Rejected verb rows: advance reviewed_at + bump error_count so an all-reject
1322
+ // batch does not loop forever. error_count ASC sort pushes them to the back.
1323
+ if (gateResult.rejected && gateResult.rejected.size > 0) {
1324
+ stats.rejected_verb = gateResult.rejected.size
1325
+ for (const id of gateResult.rejected) {
1326
+ throwIfAborted(signal)
1327
+ try {
1328
+ await db.query(`UPDATE entries SET reviewed_at = $1 WHERE id = $2`, [sweepCursor, id])
1329
+ await db.query(
1330
+ `UPDATE entries SET error_count = COALESCE(error_count, 0) + 1 WHERE id = $1`,
1331
+ [id],
1332
+ )
1333
+ } catch {}
1334
+ }
1335
+ }
1336
+
1337
+ // Flush embeddings BEFORE phase_merge: newly promoted/dirty roots have
1338
+ // NULL embeddings until the dirty queue drains, and runPhaseMerge filters
1339
+ // on `embedding IS NOT NULL` for both the cosine dedup and the core-overlap
1340
+ // pass. Running the flush after the merge would skip those rows for an
1341
+ // entire cycle. Reordering ensures same-cycle dedup/core-overlap sees them.
1342
+ try {
1343
+ throwIfAborted(signal)
1344
+ const d = await flushEmbeddingDirty(db, { signal })
1345
+ throwIfAborted(signal)
1346
+ if (d.attempted > 0) {
1347
+ __mixdogMemoryLog(
1348
+ `[cycle2] embedding flush attempted=${d.attempted} ok=${d.succeeded} failed=${d.failed.length}\n`,
1349
+ )
1350
+ }
1351
+ } catch (err) {
1352
+ if (signal?.aborted) throw signal.reason ?? err
1353
+ __mixdogMemoryLog(`[cycle2] embedding flush failed: ${err.message}\n`)
1354
+ }
1355
+
1356
+ // phase_merge: cosine dedup over active entries.
1357
+ const phaseMergeStats = await runPhaseMerge(db, { ...options, signal })
1358
+ throwIfAborted(signal)
1359
+ stats.phase_merge = phaseMergeStats
1360
+
1361
+ // Active-cap enforcement is delegated to the gate (phases 1-3): the prompt
1362
+ // exposes Active/cap counts and instructs aggressive `archived` verdicts on
1363
+ // overflow. No deterministic safety net here — if the gate ever fails to
1364
+ // contain growth, fix the prompt, not bolt a fallback back on.
1365
+
1366
+ __mixdogMemoryLog(
1367
+ `[cycle2] rescore=${stats.rescore.updated}` +
1368
+ ` core_backfill=${stats.core_embedding_backfill}` +
1369
+ ` active=${activeCount}/${activeTargetCap} review_active=${reviewActiveRows ? 1 : 0}` +
1370
+ ` | gate promoted=${stats.promoted} archived=${stats.archived}` +
1371
+ ` updated=${stats.updated} kept=${stats.kept}` +
1372
+ ` rejected_verb=${stats.rejected_verb} merge_rejected=${stats.merge_rejected}` +
1373
+ ` missing_core=${stats.missing_core_summary}` +
1374
+ ` | cascade eval=${stats.cascade.evaluated} drop=${stats.cascade.dropped}` +
1375
+ ` | phase_merge merged=${stats.phase_merge.merged} core_overlap=${stats.phase_merge.core_overlap || 0}` +
1376
+ ` llm=${stats.phase_merge.llm_calls}\n`,
1377
+ )
1378
+
1379
+ return stats
1380
+ }
1381
+
1382
+ export function parseInterval(s) {
1383
+ if (String(s).toLowerCase() === 'immediate') return 0
1384
+ const match = String(s).match(/^(\d+)(s|m|h)$/)
1385
+ if (!match) throw new Error(`[memory-cycle2] invalid interval config: ${s}`)
1386
+ const [, num, unit] = match
1387
+ const multiplier = { s: 1000, m: 60000, h: 3600000 }
1388
+ return Number(num) * multiplier[unit]
1389
+ }