mixdog 0.9.1 → 0.9.3

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 (301) hide show
  1. package/package.json +9 -1
  2. package/scripts/_bench-cwc.json +20 -0
  3. package/scripts/agent-loop-policy-test.mjs +37 -0
  4. package/scripts/agent-parallel-smoke.mjs +54 -10
  5. package/scripts/anthropic-maxtokens-test.mjs +119 -0
  6. package/scripts/background-task-meta-smoke.mjs +1 -1
  7. package/scripts/bench-run.mjs +262 -0
  8. package/scripts/build-tui.mjs +13 -1
  9. package/scripts/compact-smoke.mjs +12 -0
  10. package/scripts/compact-trigger-migration-smoke.mjs +67 -1
  11. package/scripts/explore-bench.mjs +124 -0
  12. package/scripts/hook-bus-test.mjs +191 -0
  13. package/scripts/ingest-pure-conversation-smoke.mjs +148 -0
  14. package/scripts/internal-comms-bench.mjs +727 -0
  15. package/scripts/internal-comms-smoke.mjs +75 -0
  16. package/scripts/lead-workflow-smoke.mjs +4 -4
  17. package/scripts/live-worker-smoke.mjs +9 -9
  18. package/scripts/output-style-bench.mjs +285 -0
  19. package/scripts/output-style-smoke.mjs +13 -10
  20. package/scripts/patch-replay.mjs +90 -0
  21. package/scripts/path-suffix-test.mjs +57 -0
  22. package/scripts/provider-stream-stall-test.mjs +276 -0
  23. package/scripts/provider-toolcall-test.mjs +599 -1
  24. package/scripts/recall-bench.mjs +207 -0
  25. package/scripts/routing-corpus.mjs +281 -0
  26. package/scripts/session-bench.mjs +1526 -0
  27. package/scripts/session-diag.mjs +595 -0
  28. package/scripts/task-bench.mjs +207 -0
  29. package/scripts/tool-failures.mjs +6 -6
  30. package/scripts/tool-smoke.mjs +310 -67
  31. package/scripts/toolcall-args-test.mjs +81 -0
  32. package/src/agents/debugger/AGENT.md +4 -4
  33. package/src/agents/heavy-worker/AGENT.md +20 -9
  34. package/src/agents/reviewer/AGENT.md +4 -4
  35. package/src/agents/worker/AGENT.md +17 -9
  36. package/src/app.mjs +10 -6
  37. package/src/defaults/{hidden-roles.json → agents.json} +7 -7
  38. package/src/examples/schedules/SCHEDULE.example.md +32 -0
  39. package/src/examples/webhooks/WEBHOOK.example.md +40 -0
  40. package/src/headless-role.mjs +14 -14
  41. package/src/help.mjs +1 -0
  42. package/src/lib/rules-builder.cjs +32 -54
  43. package/src/mixdog-session-runtime.mjs +1040 -2036
  44. package/src/output-styles/default.md +12 -7
  45. package/src/output-styles/minimal.md +25 -0
  46. package/src/output-styles/oneline.md +21 -0
  47. package/src/output-styles/simple.md +10 -9
  48. package/src/repl.mjs +17 -7
  49. package/src/rules/agent/00-common.md +7 -5
  50. package/src/rules/agent/30-explorer.md +8 -12
  51. package/src/rules/lead/01-general.md +3 -1
  52. package/src/rules/lead/lead-tool.md +13 -2
  53. package/src/rules/shared/01-tool.md +23 -12
  54. package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +90 -32
  55. package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +32 -0
  56. package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +18 -6
  57. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +23 -20
  58. package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +48 -14
  59. package/src/runtime/agent/orchestrator/agent-trace.mjs +87 -12
  60. package/src/runtime/agent/orchestrator/config.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/context/collect.mjs +182 -67
  62. package/src/runtime/agent/orchestrator/{internal-roles.mjs → internal-agents.mjs} +72 -72
  63. package/src/runtime/agent/orchestrator/internal-tools.mjs +13 -26
  64. package/src/runtime/agent/orchestrator/mcp/client.mjs +100 -18
  65. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -0
  66. package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +188 -0
  67. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +444 -0
  68. package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +93 -0
  69. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +313 -84
  70. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +104 -38
  71. package/src/runtime/agent/orchestrator/providers/api-usage.mjs +28 -33
  72. package/src/runtime/agent/orchestrator/providers/gemini.mjs +184 -17
  73. package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +8 -1
  74. package/src/runtime/agent/orchestrator/providers/lib/usage-primitives.mjs +32 -0
  75. package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +18 -8
  76. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +54 -20
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +227 -31
  78. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +83 -6
  79. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +229 -115
  80. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +213 -33
  81. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +18 -0
  82. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +49 -17
  83. package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -1
  84. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +22 -17
  85. package/src/runtime/agent/orchestrator/session/compact.mjs +560 -51
  86. package/src/runtime/agent/orchestrator/session/context-utils.mjs +250 -3
  87. package/src/runtime/agent/orchestrator/session/loop/compact-debug.mjs +28 -0
  88. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +262 -0
  89. package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +38 -0
  90. package/src/runtime/agent/orchestrator/session/loop/env.mjs +14 -0
  91. package/src/runtime/agent/orchestrator/session/loop/hidden-agents.mjs +21 -0
  92. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +49 -0
  93. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +63 -0
  94. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +100 -0
  95. package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +52 -0
  96. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +218 -0
  97. package/src/runtime/agent/orchestrator/session/loop/transcript-repair.mjs +101 -0
  98. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +35 -0
  99. package/src/runtime/agent/orchestrator/session/loop.mjs +513 -1000
  100. package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +227 -0
  101. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +235 -0
  102. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +137 -0
  103. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +155 -0
  104. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +303 -0
  105. package/src/runtime/agent/orchestrator/session/manager.mjs +232 -1152
  106. package/src/runtime/agent/orchestrator/session/store.mjs +4 -4
  107. package/src/runtime/agent/orchestrator/session/tool-envelope.mjs +61 -0
  108. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +5 -0
  109. package/src/runtime/agent/orchestrator/stall-policy.mjs +63 -15
  110. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +194 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +143 -0
  112. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +34 -18
  113. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +241 -0
  114. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +162 -0
  115. package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +1 -1
  116. package/src/runtime/agent/orchestrator/tools/builtin/list-formatting.mjs +10 -0
  117. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +5 -4
  118. package/src/runtime/agent/orchestrator/tools/builtin/path-diagnostics.mjs +42 -2
  119. package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +15 -0
  120. package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +9 -44
  121. package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -1
  122. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +14 -5
  123. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +11 -4
  124. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +10 -17
  125. package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +23 -2
  126. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +50 -39
  127. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -2
  128. package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +10 -0
  129. package/src/runtime/agent/orchestrator/tools/builtin.mjs +70 -1
  130. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +303 -0
  131. package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +43 -0
  132. package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +382 -0
  133. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +497 -0
  134. package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +295 -0
  135. package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +158 -0
  136. package/src/runtime/agent/orchestrator/tools/code-graph/lang-predicates.mjs +128 -0
  137. package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +66 -0
  138. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +44 -0
  139. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +1192 -0
  140. package/src/runtime/agent/orchestrator/tools/code-graph/source-access.mjs +81 -0
  141. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +19 -0
  142. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +280 -0
  143. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +347 -0
  144. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +5 -5
  145. package/src/runtime/agent/orchestrator/tools/code-graph.mjs +39 -4189
  146. package/src/runtime/agent/orchestrator/tools/patch.mjs +119 -5
  147. package/src/runtime/agent/orchestrator/tools/progress-message.mjs +1 -2
  148. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +1 -2
  149. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +2 -4
  150. package/src/runtime/channels/backends/discord.mjs +99 -9
  151. package/src/runtime/channels/backends/telegram.mjs +501 -0
  152. package/src/runtime/channels/index.mjs +437 -1439
  153. package/src/runtime/channels/lib/boot-profile.mjs +23 -0
  154. package/src/runtime/channels/lib/config.mjs +54 -2
  155. package/src/runtime/channels/lib/crash-log.mjs +106 -0
  156. package/src/runtime/channels/lib/format.mjs +4 -2
  157. package/src/runtime/channels/lib/index-drop-trace.mjs +72 -0
  158. package/src/runtime/channels/lib/output-forwarder.mjs +88 -67
  159. package/src/runtime/channels/lib/runtime-paths.mjs +29 -0
  160. package/src/runtime/channels/lib/scheduler.mjs +1 -1
  161. package/src/runtime/channels/lib/telegram-format.mjs +280 -0
  162. package/src/runtime/channels/lib/tool-format.mjs +1 -1
  163. package/src/runtime/channels/lib/transcript-discovery.mjs +19 -1
  164. package/src/runtime/channels/lib/webhook.mjs +59 -31
  165. package/src/runtime/channels/lib/whisper-language.mjs +42 -0
  166. package/src/runtime/channels/tool-defs.mjs +1 -1
  167. package/src/runtime/memory/index.mjs +465 -345
  168. package/src/runtime/memory/lib/agent-ipc.mjs +2 -2
  169. package/src/runtime/memory/lib/core-memory-store.mjs +352 -2
  170. package/src/runtime/memory/lib/cycle-signatures.mjs +34 -0
  171. package/src/runtime/memory/lib/http-wire.mjs +57 -0
  172. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -1
  173. package/src/runtime/memory/lib/memory-cycle2.mjs +65 -9
  174. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  175. package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +24 -0
  176. package/src/runtime/memory/lib/memory-retrievers.mjs +8 -0
  177. package/src/runtime/memory/lib/memory.mjs +121 -4
  178. package/src/runtime/memory/lib/pg/adapter.mjs +139 -15
  179. package/src/runtime/memory/lib/promotion-fingerprint.mjs +50 -0
  180. package/src/runtime/memory/lib/recall-format.mjs +183 -0
  181. package/src/runtime/memory/lib/session-ingest.mjs +107 -0
  182. package/src/runtime/memory/lib/trace-store.mjs +69 -22
  183. package/src/runtime/memory/tool-defs.mjs +10 -7
  184. package/src/runtime/shared/abort-controller.mjs +1 -1
  185. package/src/runtime/shared/background-tasks.mjs +2 -3
  186. package/src/runtime/shared/buffered-appender.mjs +149 -0
  187. package/src/runtime/shared/channel-notification-routing.mjs +12 -0
  188. package/src/runtime/shared/channel-notification-routing.test.mjs +45 -0
  189. package/src/runtime/shared/config.mjs +9 -0
  190. package/src/runtime/shared/llm/http-agent.mjs +12 -5
  191. package/src/runtime/shared/schedules-store.mjs +21 -19
  192. package/src/runtime/shared/task-notification-envelope.mjs +98 -0
  193. package/src/runtime/shared/task-notification-envelope.test.mjs +107 -0
  194. package/src/runtime/shared/tool-execution-contract.mjs +2 -2
  195. package/src/runtime/shared/tool-surface.mjs +98 -13
  196. package/src/runtime/shared/transcript-writer.mjs +156 -0
  197. package/src/runtime/shared/update-checker.mjs +214 -0
  198. package/src/session-runtime/config-helpers.mjs +209 -0
  199. package/src/session-runtime/effort.mjs +128 -0
  200. package/src/session-runtime/fs-utils.mjs +10 -0
  201. package/src/session-runtime/model-capabilities.mjs +130 -0
  202. package/src/session-runtime/output-styles.mjs +124 -0
  203. package/src/session-runtime/plugin-mcp.mjs +114 -0
  204. package/src/session-runtime/session-text.mjs +100 -0
  205. package/src/session-runtime/statusline-route.mjs +35 -0
  206. package/src/session-runtime/tool-catalog.mjs +720 -0
  207. package/src/session-runtime/workflow.mjs +358 -0
  208. package/src/standalone/agent-tool.mjs +302 -117
  209. package/src/standalone/channel-admin.mjs +133 -40
  210. package/src/standalone/channel-worker.mjs +10 -292
  211. package/src/standalone/explore-tool.mjs +12 -5
  212. package/src/standalone/hook-bus.mjs +165 -8
  213. package/src/standalone/memory-runtime-proxy.mjs +3 -1
  214. package/src/standalone/opencode-go-login.mjs +121 -0
  215. package/src/standalone/provider-admin.mjs +25 -3
  216. package/src/standalone/usage-dashboard.mjs +1 -1
  217. package/src/tui/App.jsx +2883 -778
  218. package/src/tui/components/ConfirmBar.jsx +47 -0
  219. package/src/tui/components/ContextPanel.jsx +5 -3
  220. package/src/tui/components/ItemRightHintOverprint.jsx +54 -0
  221. package/src/tui/components/Markdown.jsx +22 -98
  222. package/src/tui/components/Message.jsx +14 -35
  223. package/src/tui/components/Picker.jsx +87 -12
  224. package/src/tui/components/PromptInput.jsx +355 -22
  225. package/src/tui/components/QueuedCommands.jsx +1 -1
  226. package/src/tui/components/SlashCommandPalette.jsx +8 -5
  227. package/src/tui/components/Spinner.jsx +7 -7
  228. package/src/tui/components/StatusLine.jsx +40 -21
  229. package/src/tui/components/TextEntryPanel.jsx +51 -7
  230. package/src/tui/components/ToolExecution.jsx +183 -101
  231. package/src/tui/components/TurnDone.jsx +4 -4
  232. package/src/tui/components/UsagePanel.jsx +1 -1
  233. package/src/tui/components/tool-output-format.mjs +161 -23
  234. package/src/tui/components/tool-output-format.test.mjs +87 -0
  235. package/src/tui/display-width.mjs +69 -0
  236. package/src/tui/display-width.test.mjs +35 -0
  237. package/src/tui/dist/index.mjs +6731 -2333
  238. package/src/tui/engine/agent-envelope.mjs +296 -0
  239. package/src/tui/engine/boot-profile.mjs +21 -0
  240. package/src/tui/engine/labels.mjs +67 -0
  241. package/src/tui/engine/notice-text.mjs +112 -0
  242. package/src/tui/engine/queue-helpers.mjs +161 -0
  243. package/src/tui/engine/session-stats.mjs +46 -0
  244. package/src/tui/engine/tool-call-fields.mjs +23 -0
  245. package/src/tui/engine/tool-result-text.mjs +126 -0
  246. package/src/tui/engine.mjs +569 -948
  247. package/src/tui/index.jsx +117 -7
  248. package/src/tui/input-editing.mjs +58 -8
  249. package/src/tui/input-editing.selection.test.mjs +75 -0
  250. package/src/tui/keyboard-protocol.mjs +42 -0
  251. package/src/tui/lib/voice-recorder.mjs +469 -0
  252. package/src/tui/markdown/format-token.mjs +128 -76
  253. package/src/tui/markdown/format-token.test.mjs +61 -19
  254. package/src/tui/markdown/measure-rendered-rows.mjs +85 -0
  255. package/src/tui/markdown/render-ansi.test.mjs +1 -1
  256. package/src/tui/markdown/streaming-markdown.mjs +167 -0
  257. package/src/tui/markdown/streaming-markdown.test.mjs +70 -0
  258. package/src/tui/markdown/table-layout.mjs +9 -9
  259. package/src/tui/paste-attachments.mjs +36 -9
  260. package/src/tui/paste-fix.test.mjs +119 -0
  261. package/src/tui/prompt-history-store.mjs +129 -0
  262. package/src/tui/prompt-history-store.test.mjs +52 -0
  263. package/src/tui/statusline-ansi-bridge.test.mjs +3 -3
  264. package/src/tui/theme.mjs +41 -657
  265. package/src/tui/themes/base.mjs +86 -0
  266. package/src/tui/themes/basic.mjs +85 -0
  267. package/src/tui/themes/catppuccin.mjs +72 -0
  268. package/src/tui/themes/dracula.mjs +70 -0
  269. package/src/tui/themes/everforest.mjs +71 -0
  270. package/src/tui/themes/gruvbox.mjs +71 -0
  271. package/src/tui/themes/index.mjs +71 -0
  272. package/src/tui/themes/indigo.mjs +78 -0
  273. package/src/tui/themes/kanagawa.mjs +80 -0
  274. package/src/tui/themes/light.mjs +81 -0
  275. package/src/tui/themes/nord.mjs +72 -0
  276. package/src/tui/themes/onedark.mjs +16 -0
  277. package/src/tui/themes/rosepine.mjs +70 -0
  278. package/src/tui/themes/teal.mjs +80 -0
  279. package/src/tui/themes/tokyonight.mjs +79 -0
  280. package/src/tui/themes/utils.mjs +106 -0
  281. package/src/tui/themes/warm.mjs +79 -0
  282. package/src/tui/transcript-tool-failures.mjs +13 -2
  283. package/src/ui/markdown.mjs +1 -1
  284. package/src/ui/model-display.mjs +2 -2
  285. package/src/ui/statusline.mjs +75 -27
  286. package/src/vendor/statusline/bin/statusline-route.mjs +5 -12
  287. package/src/vendor/statusline/src/gateway/claude-current.mjs +3 -3
  288. package/src/vendor/statusline/src/gateway/route-meta.mjs +30 -16
  289. package/src/workflows/default/WORKFLOW.md +46 -12
  290. package/src/workflows/sequential/WORKFLOW.md +51 -0
  291. package/src/workflows/solo/WORKFLOW.md +12 -1
  292. package/vendor/ink/build/display-width.js +62 -0
  293. package/vendor/ink/build/ink.js +100 -12
  294. package/vendor/ink/build/measure-text.js +4 -1
  295. package/vendor/ink/build/output.js +115 -9
  296. package/vendor/ink/build/render-node-to-output.js +4 -1
  297. package/vendor/ink/build/render.js +4 -0
  298. package/src/output-styles/extreme-simple.md +0 -20
  299. package/src/rules/lead/04-workflow.md +0 -51
  300. package/src/workflows/default/workflow.json +0 -13
  301. package/src/workflows/solo/workflow.json +0 -7
@@ -0,0 +1,382 @@
1
+ // Per-cwd on-disk code-graph cache: manifest + <hash>.json layout, legacy
2
+ // single-file migration, budget pruning, orphan sweep, and a debounced
3
+ // atomic flush. Owns its own module-level state (the in-memory disk map,
4
+ // manifest, flush timer). Extracted verbatim from code-graph.mjs.
5
+ import { createHash } from 'node:crypto';
6
+ import { join } from 'node:path';
7
+ import {
8
+ readdirSync, readFileSync, statSync, existsSync, mkdirSync, renameSync, unlinkSync,
9
+ } from 'node:fs';
10
+ import { getPluginData } from '../../config.mjs';
11
+ import { writeJsonAtomicSync } from '../../../../shared/atomic-file.mjs';
12
+ import {
13
+ canonicalGraphCwd as _canonicalGraphCwd,
14
+ registerCodeGraphDrain,
15
+ drainCodeGraphCache as drainCodeGraphCacheState,
16
+ } from '../code-graph-state.mjs';
17
+ import {
18
+ CODE_GRAPH_DISK_FILE,
19
+ CODE_GRAPH_DISK_DIR,
20
+ CODE_GRAPH_DISK_MAX_ENTRIES,
21
+ CODE_GRAPH_DISK_MAX_BYTES,
22
+ ORPHAN_TMP_MIN_AGE_MS,
23
+ RE_CACHE_TMP,
24
+ RE_MANIFEST_TMP,
25
+ RE_CACHE_LOCK,
26
+ } from './constants.mjs';
27
+ import { _serializeGraph, _deserializeGraph } from './graph-model.mjs';
28
+
29
+ const _diskCodeGraphCache = new Map();
30
+ let _diskCodeGraphCacheLoaded = false;
31
+ let _diskCodeGraphCacheFlushTimer = null;
32
+ // Per-cwd manifest read at boot; per-cwd entries load on demand via
33
+ // _ensureCwdLoaded(cwd). Avoids the cold-start I/O spike that hit every
34
+ // fresh process when the legacy single-file cache grew unbounded.
35
+ let _diskManifest = null;
36
+
37
+ function _codeGraphDiskDir() {
38
+ return join(getPluginData(), CODE_GRAPH_DISK_DIR);
39
+ }
40
+
41
+ const _HASH_CWD_CACHE_MAX = 50;
42
+ const _hashCwdCache = new Map();
43
+
44
+ function _hashCwd(cwd) {
45
+ // Memoize SHA256(canonical cwd) — the same canonical cwd is hashed
46
+ // repeatedly on persist/sweep hot paths. Keyed by canonical cwd; capped
47
+ // so a long-lived process cycling through many cwds can't grow unbounded.
48
+ const canon = _canonicalGraphCwd(cwd);
49
+ const cached = _hashCwdCache.get(canon);
50
+ if (cached !== undefined) return cached;
51
+ const hash = createHash('sha256').update(canon).digest('hex').slice(0, 16);
52
+ if (_hashCwdCache.size >= _HASH_CWD_CACHE_MAX) {
53
+ // Evict oldest insertion (Map preserves insertion order).
54
+ _hashCwdCache.delete(_hashCwdCache.keys().next().value);
55
+ }
56
+ _hashCwdCache.set(canon, hash);
57
+ return hash;
58
+ }
59
+
60
+ function _migrateLegacyDiskCache() {
61
+ const legacy = join(getPluginData(), CODE_GRAPH_DISK_FILE);
62
+ if (!existsSync(legacy)) return;
63
+ try {
64
+ const parsed = JSON.parse(readFileSync(legacy, 'utf8'));
65
+ if (parsed && typeof parsed === 'object') {
66
+ for (const [cwd, entry] of Object.entries(parsed)) {
67
+ if (!entry || typeof entry !== 'object') continue;
68
+ _diskCodeGraphCache.set(_canonicalGraphCwd(cwd), entry);
69
+ }
70
+ }
71
+ // Rename rather than delete so a rollback can recover the blob if the
72
+ // per-cwd layout misbehaves. Next persist round writes the new layout
73
+ // and the legacy path no longer exists, so this branch is a one-shot.
74
+ renameSync(legacy, `${legacy}.bak-${Date.now()}`);
75
+ // Schedule an immediate flush so the in-memory entries we just loaded
76
+ // get written out as per-cwd files now, instead of waiting for the
77
+ // next graph rebuild to trigger _setDiskCodeGraphEntry. Without this,
78
+ // the layout transition is half-complete (legacy renamed, new layout
79
+ // empty) until an unrelated build happens to land.
80
+ _scheduleDiskCodeGraphCacheFlush();
81
+ } catch (err) {
82
+ process.stderr.write(`[code-graph] legacy cache migration failed: ${err?.message || err}\n`);
83
+ }
84
+ }
85
+
86
+ function _pruneDiskCodeGraphEntries(_now = Date.now()) {
87
+ for (const [cwd, entry] of _diskCodeGraphCache) {
88
+ if (!entry || typeof entry !== 'object') {
89
+ _diskCodeGraphCache.delete(cwd);
90
+ continue;
91
+ }
92
+ // Disk entries are not TTL-evicted: signature validation on load/build
93
+ // plus _pruneCodeGraphManifestForBudget (MIXDOG_CODE_GRAPH_CACHE_MAX_MB)
94
+ // govern freshness and size. Memory cache keeps CODE_GRAPH_TTL_MS.
95
+ }
96
+ while (_diskCodeGraphCache.size > CODE_GRAPH_DISK_MAX_ENTRIES) {
97
+ const oldest = _diskCodeGraphCache.keys().next().value;
98
+ if (!oldest) break;
99
+ _diskCodeGraphCache.delete(oldest);
100
+ }
101
+ }
102
+
103
+ function _isCodeGraphCacheHash(value) {
104
+ return /^[0-9a-f]{8,64}$/i.test(String(value || ''));
105
+ }
106
+
107
+ export function _pruneCodeGraphManifestForBudget(manifest, dir, options = {}) {
108
+ const maxEntries = Number.isFinite(options.maxEntries)
109
+ ? Math.max(0, Math.floor(options.maxEntries))
110
+ : CODE_GRAPH_DISK_MAX_ENTRIES;
111
+ const maxBytes = Number.isFinite(options.maxBytes)
112
+ ? Math.max(0, Math.floor(options.maxBytes))
113
+ : CODE_GRAPH_DISK_MAX_BYTES;
114
+ const rows = [];
115
+ for (const [cwd, meta] of Object.entries(manifest || {})) {
116
+ const hash = String(meta?.hash || '');
117
+ if (!cwd || !_isCodeGraphCacheHash(hash)) continue;
118
+ const file = join(dir, `${hash}.json`);
119
+ let size = 0;
120
+ try { size = statSync(file).size; } catch { continue; }
121
+ rows.push({
122
+ cwd,
123
+ hash,
124
+ builtAt: Number(meta?.builtAt) || 0,
125
+ size: Math.max(0, Number(size) || 0),
126
+ });
127
+ }
128
+ rows.sort((a, b) => (a.builtAt - b.builtAt) || a.cwd.localeCompare(b.cwd));
129
+ const keep = new Set(rows.map((row) => row.cwd));
130
+ let totalBytes = rows.reduce((sum, row) => sum + row.size, 0);
131
+ const evicted = [];
132
+
133
+ const evict = (row, reason) => {
134
+ if (!row || !keep.has(row.cwd)) return false;
135
+ keep.delete(row.cwd);
136
+ totalBytes -= row.size;
137
+ evicted.push({ ...row, reason });
138
+ return true;
139
+ };
140
+
141
+ for (const row of rows) {
142
+ if (keep.size <= maxEntries) break;
143
+ evict(row, 'max-entries');
144
+ }
145
+ for (const row of rows) {
146
+ if (totalBytes <= maxBytes) break;
147
+ evict(row, 'max-bytes');
148
+ }
149
+
150
+ const pruned = {};
151
+ for (const row of rows) {
152
+ if (!keep.has(row.cwd)) continue;
153
+ pruned[row.cwd] = { hash: row.hash, builtAt: row.builtAt };
154
+ }
155
+ return { manifest: pruned, evicted, totalBytes: Math.max(0, totalBytes) };
156
+ }
157
+
158
+ function _readCacheLockOwnerPid(lockPath) {
159
+ try {
160
+ const raw = readFileSync(lockPath, 'utf8');
161
+ const tok = String(raw).trim().split(/\s+/)[0];
162
+ const pid = Number.parseInt(tok, 10);
163
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
164
+ } catch {
165
+ return null;
166
+ }
167
+ }
168
+
169
+ function _cacheLockOwnerIsDead(lockPath) {
170
+ const pid = _readCacheLockOwnerPid(lockPath);
171
+ // Unparseable/unreadable owner pid: keep the lock (conservative). Truly stale
172
+ // locks are reclaimed on the next writeJsonAtomicSync via atomic-file.mjs
173
+ // stale-lock recovery (mtime > staleMs, dead owner pid).
174
+ if (pid === null) return false;
175
+ if (pid === process.pid) return false;
176
+ try {
177
+ process.kill(pid, 0);
178
+ return false;
179
+ } catch (err) {
180
+ return err?.code === 'ESRCH';
181
+ }
182
+ }
183
+
184
+ function _cacheFileOlderThanGuard(fullPath, now, minAgeMs) {
185
+ try {
186
+ const st = statSync(fullPath);
187
+ return now - st.mtimeMs > minAgeMs;
188
+ } catch {
189
+ return false;
190
+ }
191
+ }
192
+
193
+ // Best-effort orphan cleanup: evicted <hash>.json plus aged atomic-write .tmp/.lock
194
+ // left by crash/kill between temp write and rename (writeFileAtomicSync). Young temps
195
+ // are kept because a live persist may still hold the matching .lock while writing.
196
+ function _sweepCodeGraphCacheDir(dir, validHashes, opts = {}) {
197
+ const now = Number.isFinite(opts.now) ? opts.now : Date.now();
198
+ const sweepJson = opts.sweepJson !== false;
199
+ try {
200
+ for (const f of readdirSync(dir)) {
201
+ const full = join(dir, f);
202
+ if (f === 'manifest.json') continue;
203
+ if (f.endsWith('.json')) {
204
+ if (!sweepJson) continue;
205
+ const hash = f.slice(0, -5);
206
+ if (!validHashes.has(hash)) {
207
+ try { unlinkSync(full); } catch { /* best-effort */ }
208
+ }
209
+ continue;
210
+ }
211
+ if (RE_CACHE_TMP.test(f) || RE_MANIFEST_TMP.test(f)) {
212
+ if (!_cacheFileOlderThanGuard(full, now, ORPHAN_TMP_MIN_AGE_MS)) continue;
213
+ try { unlinkSync(full); } catch { /* best-effort */ }
214
+ continue;
215
+ }
216
+ if (f === 'manifest.json.lock' || RE_CACHE_LOCK.test(f)) {
217
+ if (!_cacheFileOlderThanGuard(full, now, ORPHAN_TMP_MIN_AGE_MS)) continue;
218
+ if (!_cacheLockOwnerIsDead(full)) continue;
219
+ try { unlinkSync(full); } catch { /* best-effort */ }
220
+ }
221
+ }
222
+ } catch { /* sweep best-effort */ }
223
+ }
224
+
225
+ function _loadDiskCodeGraphCache(now = Date.now()) {
226
+ if (_diskCodeGraphCacheLoaded) return;
227
+ _diskCodeGraphCacheLoaded = true;
228
+
229
+ // One-shot migration from the legacy single-file cache. Subsequent boots
230
+ // skip this branch because the source file was renamed to .bak.
231
+ _migrateLegacyDiskCache();
232
+
233
+ // Manifest-only load: per-cwd entries are picked up by _ensureCwdLoaded()
234
+ // at lookup time. Cold start now pays a single small JSON.parse instead
235
+ // of reading every per-cwd file (~24 × ~2 MB on long-running workspaces).
236
+ let manifestTrusted = false;
237
+ try {
238
+ const manifestFile = join(_codeGraphDiskDir(), 'manifest.json');
239
+ if (existsSync(manifestFile)) {
240
+ const parsed = JSON.parse(readFileSync(manifestFile, 'utf8'));
241
+ if (parsed && typeof parsed === 'object') {
242
+ _diskManifest = parsed;
243
+ manifestTrusted = true;
244
+ }
245
+ }
246
+ } catch (err) {
247
+ process.stderr.write(`[code-graph] disk manifest load failed: ${err?.message || err}\n`);
248
+ }
249
+ if (!_diskManifest) _diskManifest = {};
250
+ _pruneDiskCodeGraphEntries(now);
251
+ try {
252
+ const dir = _codeGraphDiskDir();
253
+ mkdirSync(dir, { recursive: true });
254
+ const validHashes = new Set();
255
+ for (const meta of Object.values(_diskManifest)) {
256
+ if (meta && typeof meta === 'object' && meta.hash) validHashes.add(meta.hash);
257
+ }
258
+ // Without a successfully loaded manifest we must not delete <hash>.json
259
+ // files (validHashes would be empty or incomplete after a parse failure).
260
+ _sweepCodeGraphCacheDir(dir, validHashes, { now, sweepJson: manifestTrusted });
261
+ } catch { /* boot sweep best-effort */ }
262
+ }
263
+
264
+ // Demand-load one cwd's per-file entry. Callers invoke this right before
265
+ // reading the disk cache so the in-memory cache stays populated only for
266
+ // cwds actually looked up in this process lifetime.
267
+ function _ensureCwdLoaded(cwd) {
268
+ const key = _canonicalGraphCwd(cwd);
269
+ if (_diskCodeGraphCache.has(key)) return;
270
+ if (!_diskManifest) return;
271
+ const meta = _diskManifest[key];
272
+ if (!meta || typeof meta !== 'object' || !meta.hash) return;
273
+ try {
274
+ const file = join(_codeGraphDiskDir(), `${meta.hash}.json`);
275
+ if (!existsSync(file)) return;
276
+ const entry = JSON.parse(readFileSync(file, 'utf8'));
277
+ if (entry && typeof entry === 'object') _diskCodeGraphCache.set(key, entry);
278
+ } catch { /* skip corrupt per-cwd file */ }
279
+ }
280
+
281
+ function _persistDiskCodeGraphCacheNow() {
282
+ try {
283
+ _loadDiskCodeGraphCache();
284
+ _pruneDiskCodeGraphEntries();
285
+ const dir = _codeGraphDiskDir();
286
+ mkdirSync(dir, { recursive: true });
287
+
288
+ // Read the on-disk manifest BEFORE writing so cwd entries owned by
289
+ // other instances (MIXDOG_MULTI_INSTANCE=1) survive. Without this,
290
+ // our orphan sweep below would happily unlink another instance's
291
+ // per-cwd files just because we don't have them in our in-memory map.
292
+ let preserved = {};
293
+ try {
294
+ const raw = readFileSync(join(dir, 'manifest.json'), 'utf8');
295
+ const parsed = JSON.parse(raw);
296
+ if (parsed && typeof parsed === 'object') preserved = parsed;
297
+ } catch { /* no existing manifest yet */ }
298
+
299
+ let manifest = { ...preserved };
300
+ const validHashes = new Set();
301
+ for (const [cwd, entry] of _diskCodeGraphCache) {
302
+ const hash = _hashCwd(cwd);
303
+ const file = join(dir, `${hash}.json`);
304
+ writeJsonAtomicSync(file, entry, { compact: true, lock: true });
305
+ manifest[cwd] = { hash, builtAt: entry.builtAt || Date.now() };
306
+ }
307
+ const pruned = _pruneCodeGraphManifestForBudget(manifest, dir);
308
+ manifest = pruned.manifest;
309
+ for (const row of pruned.evicted) {
310
+ _diskCodeGraphCache.delete(row.cwd);
311
+ }
312
+ for (const meta of Object.values(manifest)) {
313
+ if (meta && typeof meta === 'object' && meta.hash) validHashes.add(meta.hash);
314
+ }
315
+
316
+ const manifestFile = join(dir, 'manifest.json');
317
+ writeJsonAtomicSync(manifestFile, manifest, { compact: true, lock: true });
318
+ _diskManifest = manifest;
319
+
320
+ // Sweep orphan per-cwd files. validHashes now includes every hash in
321
+ // the merged manifest (preserved + ours) so cross-instance cache files
322
+ // are never collateral damage.
323
+ _sweepCodeGraphCacheDir(dir, validHashes, { sweepJson: true });
324
+ } catch (err) {
325
+ process.stderr.write(`[code-graph] disk cache persist failed (target: ${_codeGraphDiskDir()}): ${err?.message || err}\n`);
326
+ }
327
+ }
328
+
329
+ function _scheduleDiskCodeGraphCacheFlush() {
330
+ if (_diskCodeGraphCacheFlushTimer) return;
331
+ _diskCodeGraphCacheFlushTimer = setTimeout(() => {
332
+ _diskCodeGraphCacheFlushTimer = null;
333
+ _persistDiskCodeGraphCacheNow();
334
+ }, 250);
335
+ if (typeof _diskCodeGraphCacheFlushTimer.unref === 'function') _diskCodeGraphCacheFlushTimer.unref();
336
+ }
337
+
338
+ /**
339
+ * Sync-flush any pending code-graph disk cache write before process exit.
340
+ * Cancels the 250ms scheduled-flush timer and runs _persistDiskCodeGraphCacheNow
341
+ * directly so newly-built graphs land on disk regardless of exit timing.
342
+ */
343
+ function drainCodeGraphCacheNow() {
344
+ if (_diskCodeGraphCacheFlushTimer) {
345
+ clearTimeout(_diskCodeGraphCacheFlushTimer);
346
+ _diskCodeGraphCacheFlushTimer = null;
347
+ _persistDiskCodeGraphCacheNow();
348
+ }
349
+ }
350
+ registerCodeGraphDrain(drainCodeGraphCacheNow);
351
+
352
+ // Public: delegate to the state module's drain hook (which invokes the
353
+ // registered drainCodeGraphCacheNow). Preserves the original facade behavior.
354
+ export function drainCodeGraphCache() {
355
+ drainCodeGraphCacheState();
356
+ }
357
+
358
+ // Read a demand-loaded disk entry for `cwd` (loads it first). Returns the
359
+ // serialized payload or undefined. Callers deserialize via graph-model.
360
+ export function getDiskCodeGraphEntry(cwd) {
361
+ const key = _canonicalGraphCwd(cwd);
362
+ _ensureCwdLoaded(key);
363
+ return _diskCodeGraphCache.get(key);
364
+ }
365
+
366
+ // Ensure the on-disk manifest/sweep boot pass has run (idempotent).
367
+ export function ensureDiskCodeGraphLoaded(now = Date.now()) {
368
+ _loadDiskCodeGraphCache(now);
369
+ }
370
+
371
+ export function _setDiskCodeGraphEntry(cwd, graph) {
372
+ _loadDiskCodeGraphCache();
373
+ // Stamp the cache entry with the persistence timestamp (not the build
374
+ // start) so manifest/signature metadata stays fresh. Disk retention is
375
+ // governed by signature validation and MIXDOG_CODE_GRAPH_CACHE_MAX_MB,
376
+ // not CODE_GRAPH_TTL_MS (memory cache only).
377
+ const serialized = _serializeGraph(graph);
378
+ serialized.builtAt = Date.now();
379
+ _diskCodeGraphCache.set(_canonicalGraphCwd(cwd), serialized);
380
+ _pruneDiskCodeGraphEntries();
381
+ _scheduleDiskCodeGraphCacheFlush();
382
+ }