mixdog 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,180 @@
1
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { tmpdir } from 'os';
4
+
5
+ // This smoke exercises config-merge preservation, not the user-data backup
6
+ // path, so skip the backup entirely — otherwise updateSection writes real
7
+ // backups under ~/.claude/backups/mixdog-user-data on every run. The seed
8
+ // guard also drops `.initialized-*.json` markers into the backup ROOT (not
9
+ // gated by the skip flag), so redirect that root into the temp dir too.
10
+ process.env.MIXDOG_SKIP_USER_DATA_BACKUP = '1';
11
+
12
+ const tmp = mkdtempSync(join(tmpdir(), 'mixdog-config-preserve-'));
13
+ process.env.CLAUDE_PLUGIN_DATA = tmp;
14
+ process.env.MIXDOG_USER_DATA_BACKUP_ROOT = join(tmp, 'backups');
15
+ mkdirSync(tmp, { recursive: true });
16
+
17
+ const { updateSection, readSection } = await import('../src/shared/config.mjs');
18
+ const {
19
+ mergeConfig,
20
+ mergeAgentConfig,
21
+ mergeMemoryConfig,
22
+ mergeSearchConfig,
23
+ mergeEndpointConfig,
24
+ mergeWebhookEndpointConfig,
25
+ } = await import('../setup/config-merge.mjs');
26
+
27
+ let failed = 0;
28
+ function assert(label, cond) {
29
+ const pass = !!cond;
30
+ process.stdout.write(`${label}: ${pass ? 'PASS' : 'FAIL'}\n`);
31
+ if (!pass) failed++;
32
+ }
33
+
34
+ // (a) ngrokDomain survives partial webhook save
35
+ updateSection('channels', () => ({
36
+ backend: 'discord',
37
+ webhook: { enabled: true, ngrokDomain: 'x.ngrok-free.dev' },
38
+ }));
39
+ updateSection('channels', (current) => mergeConfig(current, {
40
+ webhook: { enabled: true, respectQuiet: false },
41
+ }));
42
+ const chA = readSection('channels');
43
+ assert('webhook.ngrokDomain preserved', chA?.webhook?.ngrokDomain === 'x.ngrok-free.dev');
44
+ assert('webhook.respectQuiet updated', chA?.webhook?.respectQuiet === false);
45
+
46
+ // (d) a plaintext authtoken in the on-disk webhook is NEVER re-persisted
47
+ // (keychain-only invariant) while ngrokDomain still survives the merge.
48
+ updateSection('channels', () => ({
49
+ backend: 'discord',
50
+ webhook: { enabled: true, ngrokDomain: 'y.ngrok-free.dev', authtoken: 'PLAINTEXT-MUST-DROP' },
51
+ }));
52
+ updateSection('channels', (current) => mergeConfig(current, {
53
+ webhook: { enabled: true, respectQuiet: true },
54
+ }));
55
+ const chD = readSection('channels');
56
+ assert('webhook plaintext authtoken stripped', chD?.webhook?.authtoken === undefined);
57
+ assert('webhook.ngrokDomain survives authtoken strip', chD?.webhook?.ngrokDomain === 'y.ngrok-free.dev');
58
+
59
+ // (b) schedules.respectQuiet sidecar survives partial schedules save
60
+ updateSection('channels', (current) => ({
61
+ ...current,
62
+ schedules: { respectQuiet: false, customSidecar: 'keep-me' },
63
+ }));
64
+ updateSection('channels', (current) => mergeConfig(current, {
65
+ schedules: { respectQuiet: true },
66
+ }));
67
+ const chB = readSection('channels');
68
+ assert('schedules.customSidecar preserved', chB?.schedules?.customSidecar === 'keep-me');
69
+ assert('schedules.respectQuiet updated', chB?.schedules?.respectQuiet === true);
70
+
71
+ // (c) agent unmanaged top-level field survives partial bridge save
72
+ updateSection('agent', () => ({
73
+ customSidecar: 'agent-keep',
74
+ bridge: { maxWorkers: 2 },
75
+ }));
76
+ updateSection('agent', (current) => mergeAgentConfig(current, { bridge: { timeoutMs: 90000 } }));
77
+ const agent = readSection('agent');
78
+ assert('agent.customSidecar preserved', agent?.customSidecar === 'agent-keep');
79
+ assert('agent.bridge merged', agent?.bridge?.maxWorkers === 2 && agent?.bridge?.timeoutMs === 90000);
80
+
81
+ // (e) schedule endpoint config.json — REAL disk round-trip: skill sidecars
82
+ // (timezone/mode) survive a partial UI edit-save.
83
+ {
84
+ const dir = join(tmp, 'schedules', 'sched1');
85
+ mkdirSync(dir, { recursive: true });
86
+ const cfgPath = join(dir, 'config.json');
87
+ writeFileSync(cfgPath, JSON.stringify({ time: '09:00', days: 'daily', timezone: 'America/Chicago', mode: 'cron' }, null, 2));
88
+ const incoming = { time: '10:00', days: 'weekday', model: 'fast', enabled: true };
89
+ const merged = mergeEndpointConfig(JSON.parse(readFileSync(cfgPath, 'utf8')), incoming);
90
+ writeFileSync(cfgPath, JSON.stringify(merged, null, 2));
91
+ const after = JSON.parse(readFileSync(cfgPath, 'utf8'));
92
+ assert('schedule.timezone preserved (disk)', after.timezone === 'America/Chicago');
93
+ assert('schedule.mode preserved (disk)', after.mode === 'cron');
94
+ assert('schedule.time updated (disk)', after.time === '10:00');
95
+ }
96
+
97
+ // (f) webhook endpoint config.json — REAL disk round-trip: omitted secret +
98
+ // parser survive a partial edit-save, and role stays pinned to webhook-handler.
99
+ {
100
+ const dir = join(tmp, 'webhooks', 'wh1');
101
+ mkdirSync(dir, { recursive: true });
102
+ const cfgPath = join(dir, 'config.json');
103
+ writeFileSync(cfgPath, JSON.stringify({ secret: 'hmac-key', parser: 'github', channel: 'main', role: 'webhook-handler' }, null, 2));
104
+ const incoming = { channel: 'alerts', model: 'default' };
105
+ const merged = mergeWebhookEndpointConfig(JSON.parse(readFileSync(cfgPath, 'utf8')), incoming);
106
+ writeFileSync(cfgPath, JSON.stringify(merged, null, 2));
107
+ const after = JSON.parse(readFileSync(cfgPath, 'utf8'));
108
+ assert('webhook.secret preserved (disk)', after.secret === 'hmac-key');
109
+ assert('webhook.parser preserved (disk)', after.parser === 'github');
110
+ assert('webhook.channel updated (disk)', after.channel === 'alerts');
111
+ assert('webhook.role pinned (disk)', after.role === 'webhook-handler');
112
+ }
113
+
114
+ // (g) mergeKept hardening: a stray array/string on EITHER side never spreads
115
+ // into numeric keys.
116
+ {
117
+ const m1 = mergeEndpointConfig({ keep: 1 }, ['x']);
118
+ assert('mergeKept ignores array incoming', m1.keep === 1 && m1['0'] === undefined);
119
+ const m2 = mergeEndpointConfig(['y'], { keep: 2 });
120
+ assert('mergeKept ignores array existing', m2.keep === 2 && m2['0'] === undefined);
121
+ }
122
+
123
+ // (h) memory — partial save preserves providers / user / cycle1 omitted keys
124
+ // (POST /memory/config → updateSection + mergeMemoryConfig).
125
+ updateSection('memory', () => ({
126
+ providers: { openai: { model: 'gpt-4o', temperature: 0.2 } },
127
+ user: { name: 'Alice', title: 'Engineer' },
128
+ cycle1: { interval: 60, timeout: 120000, batchSize: 8 },
129
+ }));
130
+ updateSection('memory', (current) => mergeMemoryConfig(current, {
131
+ cycle1: { interval: 90 },
132
+ }));
133
+ const mem = readSection('memory');
134
+ assert('memory.providers preserved', mem?.providers?.openai?.model === 'gpt-4o' && mem?.providers?.openai?.temperature === 0.2);
135
+ assert('memory.user preserved', mem?.user?.name === 'Alice' && mem?.user?.title === 'Engineer');
136
+ assert('memory.cycle1 omitted keys preserved', mem?.cycle1?.timeout === 120000 && mem?.cycle1?.batchSize === 8);
137
+ assert('memory.cycle1.interval updated', mem?.cycle1?.interval === 90);
138
+
139
+ // (i) search — partial save preserves provider / models / crawl omitted keys
140
+ // (POST /search/config → updateSection + mergeSearchConfig).
141
+ updateSection('search', () => ({
142
+ provider: 'tavily',
143
+ models: { anthropic: 'claude-haiku', openai: 'gpt-4o-mini' },
144
+ crawl: { enabled: true, maxDepth: 2, respectRobots: true },
145
+ requestTimeoutMs: 30000,
146
+ rawSearch: { maxResults: 8, credentials: {} },
147
+ }));
148
+ updateSection('search', (current) => mergeSearchConfig(current, {
149
+ requestTimeoutMs: 45000,
150
+ }));
151
+ const sr = readSection('search');
152
+ assert('search.provider preserved', sr?.provider === 'tavily');
153
+ assert('search.models preserved', sr?.models?.anthropic === 'claude-haiku' && sr?.models?.openai === 'gpt-4o-mini');
154
+ assert('search.crawl preserved', sr?.crawl?.enabled === true && sr?.crawl?.maxDepth === 2 && sr?.crawl?.respectRobots === true);
155
+ assert('search.requestTimeoutMs updated', sr?.requestTimeoutMs === 45000);
156
+
157
+ // (j) channels.channelsConfig — intentional FULL REPLACE: deleting a channel in
158
+ // the UI must drop its disk row (no stale entry); a partial save that omits
159
+ // channelsConfig entirely must preserve the existing map (length>0 guard).
160
+ updateSection('channels', () => ({
161
+ backend: 'discord',
162
+ channelsConfig: {
163
+ 'chan-a': { name: 'alpha', enabled: true },
164
+ 'chan-b': { name: 'beta', enabled: true },
165
+ },
166
+ }));
167
+ updateSection('channels', (current) => mergeConfig(current, {
168
+ channelsConfig: { 'chan-a': { name: 'alpha', enabled: false } },
169
+ }));
170
+ const chJ1 = readSection('channels');
171
+ assert('channelsConfig deleted channel removed', chJ1?.channelsConfig?.['chan-b'] === undefined);
172
+ assert('channelsConfig kept channel updated', chJ1?.channelsConfig?.['chan-a']?.enabled === false);
173
+ updateSection('channels', (current) => mergeConfig(current, {
174
+ webhook: { enabled: true },
175
+ }));
176
+ const chJ2 = readSection('channels');
177
+ assert('channelsConfig survives unrelated partial save', chJ2?.channelsConfig?.['chan-a']?.name === 'alpha');
178
+
179
+ try { rmSync(tmp, { recursive: true, force: true }); } catch {}
180
+ process.exit(failed ? 1 : 0);
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * mixdog doctor - health diagnostics for the mixdog plugin.
4
+ * Usage:
5
+ * bun scripts/doctor.mjs # run checks only
6
+ * bun scripts/doctor.mjs --setup # run checks then launch setup UI
7
+ *
8
+ * Pure Node/Bun built-ins only. No external deps. Cross-OS.
9
+ * Exit 0 unless FAIL count > 0, then exit 1.
10
+ */
11
+ import { existsSync, readFileSync, statSync, readdirSync, lstatSync } from 'fs';
12
+ import { join, basename, dirname } from 'path';
13
+ import { homedir, tmpdir } from 'os';
14
+ import { spawnSync, spawn } from 'child_process';
15
+ import { fileURLToPath } from 'url';
16
+ import { createConnection } from 'net';
17
+ import { createRequire } from 'module';
18
+
19
+ const __dirname = dirname(fileURLToPath(import.meta.url));
20
+ const require = createRequire(import.meta.url);
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Resolve PLUGIN_ROOT and PLUGIN_DATA
24
+ // ---------------------------------------------------------------------------
25
+ const PLUGIN_ROOT = process.env.CLAUDE_PLUGIN_ROOT || dirname(__dirname);
26
+
27
+ function resolvePluginData() {
28
+ if (process.env.CLAUDE_PLUGIN_DATA) return process.env.CLAUDE_PLUGIN_DATA;
29
+ const dirName = basename(PLUGIN_ROOT);
30
+ // Cache layout: <plugin>/<marketplace>/<semver>/
31
+ if (/^\d+\.\d+\.\d+/.test(dirName)) {
32
+ const pluginName = basename(join(PLUGIN_ROOT, '..'));
33
+ const marketplace = basename(join(PLUGIN_ROOT, '..', '..'));
34
+ return join(homedir(), '.claude', 'plugins', 'data', `${pluginName}-${marketplace}`);
35
+ }
36
+ // Marketplace layout: root IS the plugin dir
37
+ const marketplace = dirName;
38
+ let pluginName = 'mixdog';
39
+ try {
40
+ const manifest = JSON.parse(readFileSync(join(PLUGIN_ROOT, '.claude-plugin', 'plugin.json'), 'utf8'));
41
+ if (typeof manifest?.name === 'string' && manifest.name.trim()) pluginName = manifest.name.trim();
42
+ } catch { /* fall through */ }
43
+ return join(homedir(), '.claude', 'plugins', 'data', `${pluginName}-${marketplace}`);
44
+ }
45
+
46
+ const PLUGIN_DATA = resolvePluginData();
47
+ const isSetup = process.argv.includes('--setup');
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Result tracking
51
+ // ---------------------------------------------------------------------------
52
+ const rows = [];
53
+ let failCount = 0;
54
+ let warnCount = 0;
55
+ let okCount = 0;
56
+
57
+ function addRow(status, label, detail) {
58
+ rows.push({ status, label, detail: detail ?? '' });
59
+ if (status === 'FAIL') failCount++;
60
+ else if (status === 'WARN') warnCount++;
61
+ else okCount++;
62
+ }
63
+ const ok = (label, detail) => addRow('OK', label, detail);
64
+ const warn = (label, detail) => addRow('WARN', label, detail);
65
+ const fail = (label, detail) => addRow('FAIL', label, detail);
66
+
67
+ // ---------------------------------------------------------------------------
68
+ // Helpers
69
+ // ---------------------------------------------------------------------------
70
+ function readJson(p) {
71
+ try { return JSON.parse(readFileSync(p, 'utf8')); } catch { return null; }
72
+ }
73
+
74
+ function parsePositivePid(value) {
75
+ const pid = Number(value);
76
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
77
+ }
78
+
79
+ function getActiveOwnerPid(inst) {
80
+ return parsePositivePid(inst?.ownerLeadPid)
81
+ ?? parsePositivePid(inst?.terminalLeadPid)
82
+ ?? parsePositivePid(inst?.supervisor_pid)
83
+ ?? parsePositivePid(inst?.instanceId);
84
+ }
85
+
86
+ function dirSize(p, limitBytes = Infinity) {
87
+ let total = 0;
88
+ try {
89
+ const entries = readdirSync(p, { withFileTypes: true });
90
+ for (const e of entries) {
91
+ if (total >= limitBytes) break;
92
+ const full = join(p, e.name);
93
+ try {
94
+ if (e.isDirectory()) total += dirSize(full, limitBytes - total);
95
+ else total += statSync(full).size;
96
+ } catch { /* skip inaccessible */ }
97
+ }
98
+ } catch { /* skip inaccessible dir */ }
99
+ return total;
100
+ }
101
+
102
+ function toMB(bytes) { return (bytes / 1024 / 1024).toFixed(1); }
103
+
104
+ function semverGte(a, b) {
105
+ // Returns true if version string a >= b (numeric segments only)
106
+ const pa = a.replace(/[^0-9.]/g, '').split('.').map(Number);
107
+ const pb = b.replace(/[^0-9.]/g, '').split('.').map(Number);
108
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
109
+ const na = pa[i] ?? 0, nb = pb[i] ?? 0;
110
+ if (na > nb) return true;
111
+ if (na < nb) return false;
112
+ }
113
+ return true;
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Check 1: Version sync (package.json vs .claude-plugin/plugin.json)
118
+ // ---------------------------------------------------------------------------
119
+ (function checkVersionSync() {
120
+ const pkgPath = join(PLUGIN_ROOT, 'package.json');
121
+ const pluginPath = join(PLUGIN_ROOT, '.claude-plugin', 'plugin.json');
122
+ const pkg = readJson(pkgPath);
123
+ const plugin = readJson(pluginPath);
124
+ if (!pkg) { fail('Version sync', `cannot read ${pkgPath}`); return; }
125
+ if (!plugin) { fail('Version sync', `cannot read ${pluginPath}`); return; }
126
+ const pv = pkg.version ?? '(missing)';
127
+ const pjv = plugin.version ?? '(missing)';
128
+ if (pv === pjv) ok('Version sync', `${pv}`);
129
+ else fail('Version sync', `package.json=${pv} vs plugin.json=${pjv}`);
130
+ })();
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Check 2: Bun version >= 1.1.0
134
+ // ---------------------------------------------------------------------------
135
+ (function checkBunVersion() {
136
+ // When running under bun, Bun.version is available; otherwise fall back to CLI.
137
+ let version = typeof Bun !== 'undefined' ? Bun.version : null;
138
+ if (!version) {
139
+ const r = spawnSync('bun', ['--version'], { encoding: 'utf8' });
140
+ version = (r.stdout ?? '').trim();
141
+ }
142
+ if (!version) { fail('Bun version', 'bun not found on PATH'); return; }
143
+ const clean = version.replace(/^v/, '');
144
+ if (semverGte(clean, '1.1.0')) ok('Bun version', `${clean} (>= 1.1.0)`);
145
+ else fail('Bun version', `${clean} < 1.1.0 - upgrade required`);
146
+ })();
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Check 3: Bun source (system PATH vs plugin-local vs absent)
150
+ // ---------------------------------------------------------------------------
151
+ (function checkBunSource() {
152
+ const isWin = process.platform === 'win32';
153
+ // Try system PATH first.
154
+ const whichCmd = isWin ? 'where.exe' : 'which';
155
+ const r = spawnSync(whichCmd, ['bun'], { encoding: 'utf8' });
156
+ if (r.status === 0 && r.stdout) {
157
+ const first = r.stdout.split(/\r?\n/).map(l => l.trim()).find(l => l.length > 0);
158
+ if (first && existsSync(first)) { ok('Bun source', `system: ${first}`); return; }
159
+ }
160
+ // Check plugin-local node_modules/.bin/bun.
161
+ const localBin = join(PLUGIN_ROOT, 'node_modules', '.bin', isWin ? 'bun.exe' : 'bun');
162
+ if (existsSync(localBin)) { ok('Bun source', `local: ${localBin}`); return; }
163
+ // Not found - bootstrap will install on first launch.
164
+ warn('Bun source', 'not found (bootstrap will install via npm on first launch)');
165
+ })();
166
+
167
+ // ---------------------------------------------------------------------------
168
+ // Check 4: Shell availability
169
+ // ---------------------------------------------------------------------------
170
+ (function checkShellSource() {
171
+ const isWindows = process.platform === 'win32'
172
+ || !!(process.env.WINDIR || process.env.SystemRoot);
173
+ if (!isWindows) {
174
+ if (existsSync('/bin/sh')) ok('Shell source', '/bin/sh');
175
+ else fail('Shell source', '/bin/sh not found');
176
+ return;
177
+ }
178
+ const explicit = process.env.CLAUDE_CODE_SHELL;
179
+ if (explicit) {
180
+ const stem = basename(explicit).toLowerCase().replace(/\.exe$/, '');
181
+ if (stem !== 'pwsh' && stem !== 'powershell') {
182
+ warn('Shell source', `CLAUDE_CODE_SHELL is ignored on Windows unless it is PowerShell/pwsh: ${explicit}`);
183
+ } else if (existsSync(explicit)) {
184
+ ok('Shell source', `PowerShell: ${explicit}`);
185
+ return;
186
+ } else {
187
+ warn('Shell source', `CLAUDE_CODE_SHELL set but not found: ${explicit}`);
188
+ }
189
+ }
190
+ const candidates = [
191
+ join(process.env.ProgramFiles || 'C:\\Program Files', 'PowerShell', '7', 'pwsh.exe'),
192
+ join(process.env.SystemRoot || process.env.WINDIR || 'C:\\Windows', 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe'),
193
+ ];
194
+ for (const c of candidates) {
195
+ if (existsSync(c)) { ok('Shell source', `PowerShell: ${c}`); return; }
196
+ }
197
+ const r = spawnSync('where.exe', ['powershell.exe'], { encoding: 'utf8' });
198
+ const found = (r.stdout || '').split(/\r?\n/).map(l => l.trim()).find(l => l && existsSync(l));
199
+ if (found) ok('Shell source', `PowerShell: ${found}`);
200
+ else fail('Shell source', 'PowerShell not found');
201
+ })();
202
+
203
+ // ---------------------------------------------------------------------------
204
+ // Check 4: Active supervisor
205
+ // ---------------------------------------------------------------------------
206
+ (function checkSupervisor() {
207
+ const instancePath = join(tmpdir(), 'mixdog', 'active-instance.json');
208
+ if (!existsSync(instancePath)) {
209
+ warn('Supervisor', 'active-instance.json not found - supervisor not running');
210
+ return;
211
+ }
212
+ const inst = readJson(instancePath);
213
+ if (!inst) { warn('Supervisor', 'active-instance.json unreadable or invalid JSON'); return; }
214
+ const pid = getActiveOwnerPid(inst);
215
+ if (!pid) { warn('Supervisor', 'owner PID missing in active-instance.json'); return; }
216
+ let alive = false;
217
+ try { process.kill(pid, 0); alive = true; } catch { alive = false; }
218
+ if (!alive) { warn('Supervisor', `ownerPid=${pid} is not alive`); return; }
219
+ const ready = inst.backendReady === true;
220
+ if (ready) ok('Supervisor', `ownerPid=${pid} alive, backendReady=true`);
221
+ else warn('Supervisor', `ownerPid=${pid} alive but backendReady=${inst.backendReady} — permission hooks may be fail-open (bypassed) until the daemon is ready`);
222
+ })();
223
+
224
+ // ---------------------------------------------------------------------------
225
+ // Check 4.5: Hook IPC pipe reachability (fail-open warning)
226
+ // ---------------------------------------------------------------------------
227
+ // Hooks are fail-open by design: if the daemon/pipe is down, the shim exits 0
228
+ // and the server emits "null", so nothing is blocked. Surface that clearly so
229
+ // an operator knows permission hooks are bypassed until the daemon is restored.
230
+ // See SECURITY.md "Fail-open Hook Model".
231
+ await (async function checkHookPipe() {
232
+ let pipePath;
233
+ try {
234
+ pipePath = require('../lib/hook-pipe-path.cjs')();
235
+ } catch (err) {
236
+ warn('Hook pipe', `cannot resolve pipe path: ${err?.message || String(err)}`);
237
+ return;
238
+ }
239
+ const reachable = await new Promise((resolve) => {
240
+ let done = false;
241
+ const finish = (val) => { if (!done) { done = true; resolve(val); } };
242
+ let sock;
243
+ try {
244
+ sock = createConnection(pipePath);
245
+ } catch { finish(false); return; }
246
+ const timer = setTimeout(() => { try { sock.destroy(); } catch {} finish(false); }, 1000);
247
+ sock.once('connect', () => { clearTimeout(timer); try { sock.destroy(); } catch {} finish(true); });
248
+ sock.once('error', () => { clearTimeout(timer); try { sock.destroy(); } catch {} finish(false); });
249
+ });
250
+ if (reachable) {
251
+ ok('Hook pipe', `reachable at ${pipePath}`);
252
+ } else {
253
+ warn('Hook pipe', `unreachable at ${pipePath} — permission hooks are FAIL-OPEN (bypassed); restart the daemon to restore them`);
254
+ }
255
+ })();
256
+
257
+ // ---------------------------------------------------------------------------
258
+ // Check 5: Plugin DATA dir - required config files
259
+ // ---------------------------------------------------------------------------
260
+ (function checkDataDir() {
261
+ const configFile = join(PLUGIN_DATA, 'mixdog-config.json');
262
+ const workflowFile = join(PLUGIN_DATA, 'user-workflow.json');
263
+ const missingFiles = [];
264
+ if (!existsSync(configFile)) missingFiles.push('mixdog-config.json');
265
+ if (!existsSync(workflowFile)) missingFiles.push('user-workflow.json');
266
+ if (missingFiles.length === 0) ok('Plugin DATA dir', PLUGIN_DATA);
267
+ else warn('Plugin DATA dir', `missing: ${missingFiles.join(', ')} in ${PLUGIN_DATA}`);
268
+ })();
269
+
270
+ // ---------------------------------------------------------------------------
271
+ // Check 6: Cache layout - ~/.claude/plugins/cache/trib-plugin/mixdog/<ver>/
272
+ // ---------------------------------------------------------------------------
273
+ (function checkCacheLayout() {
274
+ const cacheBase = join(homedir(), '.claude', 'plugins', 'cache', 'trib-plugin', 'mixdog');
275
+ if (!existsSync(cacheBase)) {
276
+ warn('Cache layout', `cache dir not found: ${cacheBase}`);
277
+ return;
278
+ }
279
+ let entries;
280
+ try { entries = readdirSync(cacheBase, { withFileTypes: true }); } catch {
281
+ warn('Cache layout', `cannot read ${cacheBase}`);
282
+ return;
283
+ }
284
+ const verDirs = entries.filter(e => e.isDirectory());
285
+ if (verDirs.length === 0) { warn('Cache layout', 'no version directories found'); return; }
286
+
287
+ const THRESHOLD = 100 * 1024 * 1024; // 100 MB
288
+ let anyFail = false;
289
+ for (const vd of verDirs) {
290
+ const verPath = join(cacheBase, vd.name);
291
+ const nmPath = join(verPath, 'node_modules');
292
+ if (!existsSync(nmPath)) {
293
+ ok(`Cache ${vd.name}`, 'no node_modules');
294
+ continue;
295
+ }
296
+ let isJunction = false;
297
+ try {
298
+ const lst = lstatSync(nmPath);
299
+ isJunction = lst.isSymbolicLink();
300
+ } catch { /* treat as real */ }
301
+ const kind = isJunction ? 'junction/symlink' : 'real';
302
+ if (isJunction) {
303
+ ok(`Cache ${vd.name}`, `node_modules=${kind}`);
304
+ } else {
305
+ const sz = dirSize(nmPath, THRESHOLD + 1);
306
+ const mb = toMB(sz);
307
+ if (sz > THRESHOLD) {
308
+ warn(`Cache ${vd.name}`, `node_modules=${kind} ${mb} MB (> 100 MB - regression risk)`);
309
+ anyFail = true;
310
+ } else {
311
+ ok(`Cache ${vd.name}`, `node_modules=${kind} ${mb} MB`);
312
+ }
313
+ }
314
+ }
315
+ })();
316
+
317
+ // ---------------------------------------------------------------------------
318
+ // Check 7: Voice runtime
319
+ // ---------------------------------------------------------------------------
320
+ await (async function checkVoiceRuntime() {
321
+ try {
322
+ const { resolveVoiceRuntime } = await import('../src/channels/lib/voice-runtime-fetcher.mjs');
323
+ const runtime = resolveVoiceRuntime(PLUGIN_DATA);
324
+ if (runtime?.installed) {
325
+ ok('Voice runtime', `${runtime.kind}: whisper=${basename(runtime.whisperCmd || '')}, ffmpeg=${basename(runtime.ffmpegPath || '')}, model=${runtime.modelName || basename(runtime.modelPath || '')}`);
326
+ return;
327
+ }
328
+ const missing = [
329
+ runtime?.binary ? null : 'binary',
330
+ runtime?.model ? null : 'model',
331
+ runtime?.ffmpeg ? null : 'ffmpeg',
332
+ ].filter(Boolean);
333
+ warn('Voice runtime', `not installed - missing: ${missing.join(', ')}`);
334
+ } catch (err) {
335
+ warn('Voice runtime', `check failed: ${err?.message || String(err)}`);
336
+ }
337
+ })();
338
+
339
+ // ---------------------------------------------------------------------------
340
+ // Check 8: Required npm dependencies
341
+ // ---------------------------------------------------------------------------
342
+ (function checkDeps() {
343
+ const required = [
344
+ '@modelcontextprotocol/sdk',
345
+ 'zod',
346
+ 'openai',
347
+ 'pg',
348
+ ];
349
+ // Search in PLUGIN_ROOT/node_modules (typical) and parent cache node_modules.
350
+ const nmCandidates = [
351
+ join(PLUGIN_ROOT, 'node_modules'),
352
+ join(PLUGIN_ROOT, '..', 'node_modules'),
353
+ ];
354
+ const missing = [];
355
+ for (const dep of required) {
356
+ const found = nmCandidates.some(nm => existsSync(join(nm, dep)));
357
+ if (!found) missing.push(dep);
358
+ }
359
+ if (missing.length === 0) ok('Required deps', required.join(', '));
360
+ else fail('Required deps', `missing: ${missing.join(', ')}`);
361
+ })();
362
+
363
+ // ---------------------------------------------------------------------------
364
+ // Check 9.5: Cache / sidecar storage sizes
365
+ // ---------------------------------------------------------------------------
366
+ (function checkCacheStorage() {
367
+ const targets = [
368
+ { p: join(PLUGIN_DATA, 'code-graph-cache.json'), label: 'code-graph-cache.json' },
369
+ { p: join(PLUGIN_DATA, 'code-graph-cache'), label: 'code-graph-cache/' },
370
+ { p: join(PLUGIN_DATA, 'sidecars'), label: 'sidecars/' },
371
+ { p: join(PLUGIN_DATA, 'sessions-archive'), label: 'sessions-archive/' },
372
+ { p: join(PLUGIN_DATA, 'shell-output'), label: 'shell-output/' },
373
+ { p: join(PLUGIN_DATA, 'shell-jobs'), label: 'shell-jobs/' },
374
+ ];
375
+ const WARN_THRESHOLD = 100 * 1024 * 1024; // 100 MB per target
376
+ const lines = [];
377
+ const oversized = [];
378
+ for (const { p, label } of targets) {
379
+ if (!existsSync(p)) continue;
380
+ let sz = 0;
381
+ try {
382
+ const st = statSync(p);
383
+ sz = st.isDirectory() ? dirSize(p, WARN_THRESHOLD * 2 + 1) : st.size;
384
+ } catch { continue; }
385
+ if (sz === 0) continue;
386
+ const mb = toMB(sz);
387
+ lines.push(`${label}=${mb} MB`);
388
+ if (sz > WARN_THRESHOLD) oversized.push(`${label} ${mb} MB`);
389
+ }
390
+ if (lines.length === 0) ok('Cache storage', 'no cache artefacts');
391
+ else if (oversized.length > 0) warn('Cache storage', `oversized (> 100 MB): ${oversized.join(', ')} | all: ${lines.join(', ')}`);
392
+ else ok('Cache storage', lines.join(', '));
393
+ })();
394
+
395
+ // ---------------------------------------------------------------------------
396
+ // Check 9: Log file sizes
397
+ // ---------------------------------------------------------------------------
398
+ (function checkLogs() {
399
+ const WARN_THRESHOLD = 50 * 1024 * 1024; // 50 MB
400
+ let logEntries;
401
+ try {
402
+ logEntries = readdirSync(PLUGIN_DATA, { withFileTypes: true })
403
+ .filter(e => e.isFile() && e.name.endsWith('.log'));
404
+ } catch {
405
+ ok('Logs', 'data dir not yet created or no log files');
406
+ return;
407
+ }
408
+ if (logEntries.length === 0) { ok('Logs', 'no log files'); return; }
409
+ const large = [];
410
+ const lines = [];
411
+ for (const e of logEntries) {
412
+ const p = join(PLUGIN_DATA, e.name);
413
+ let sz = 0;
414
+ try { sz = statSync(p).size; } catch { /* skip */ }
415
+ const mb = toMB(sz);
416
+ lines.push({ name: e.name, size: sz, text: `${e.name}=${mb} MB` });
417
+ if (sz > WARN_THRESHOLD) large.push(`${e.name} ${mb} MB`);
418
+ }
419
+ if (large.length > 0) warn('Logs', `oversized (> 50 MB): ${large.join(', ')}`);
420
+ else if (lines.length > 300) warn('Logs', `${lines.length} files (> 300) — likely stale per-PID log accumulation`);
421
+ else {
422
+ lines.sort((a, b) => b.size - a.size);
423
+ const shown = lines.slice(0, 8).map(e => e.text);
424
+ const suffix = lines.length > shown.length ? `, +${lines.length - shown.length} more` : '';
425
+ ok('Logs', `${lines.length} files; largest: ${shown.join(', ')}${suffix}`);
426
+ }
427
+ })();
428
+
429
+ // ---------------------------------------------------------------------------
430
+ // Check 9.7: Memory DB size (Postgres pgdata)
431
+ // ---------------------------------------------------------------------------
432
+ (function checkMemoryDbSize() {
433
+ const pgdata = join(PLUGIN_DATA, 'pgdata');
434
+ if (!existsSync(pgdata)) { ok('Memory DB size', 'pgdata not found'); return; }
435
+ const WARN_THRESHOLD = 1024 * 1024 * 1024; // 1 GB
436
+ let total = 0;
437
+ try { total = dirSize(pgdata); } catch { warn('Memory DB size', `cannot read ${pgdata}`); return; }
438
+ const baseBytes = existsSync(join(pgdata, 'base')) ? dirSize(join(pgdata, 'base')) : 0;
439
+ const walBytes = existsSync(join(pgdata, 'pg_wal')) ? dirSize(join(pgdata, 'pg_wal')) : 0;
440
+ const detail = `pgdata ${toMB(total)} MB (base ${toMB(baseBytes)}, wal ${toMB(walBytes)})`;
441
+ if (total >= WARN_THRESHOLD) warn('Memory DB size', `${detail} — >= 1 GB, review memory retention`);
442
+ else ok('Memory DB size', detail);
443
+ })();
444
+
445
+ // ---------------------------------------------------------------------------
446
+ // Print report
447
+ // ---------------------------------------------------------------------------
448
+ const SEP = '-'.repeat(62);
449
+ console.log('');
450
+ console.log('mixdog doctor');
451
+ console.log(SEP);
452
+ for (const { status, label, detail } of rows) {
453
+ const tag = `[${status}]`.padEnd(6);
454
+ console.log(`${tag} ${label}${detail ? ': ' + detail : ''}`);
455
+ }
456
+ console.log(SEP);
457
+ console.log(`${okCount} OK ${warnCount} WARN ${failCount} FAIL`);
458
+ console.log('');
459
+
460
+ // ---------------------------------------------------------------------------
461
+ // --setup: launch setup UI after report
462
+ // ---------------------------------------------------------------------------
463
+ if (isSetup) {
464
+ const launchScript = join(PLUGIN_ROOT, 'setup', 'launch.mjs');
465
+ if (!existsSync(launchScript)) {
466
+ console.error(`[FAIL] setup launcher not found: ${launchScript}`);
467
+ process.exit(1);
468
+ }
469
+ console.log('Launching setup UI...');
470
+ const child = spawn(process.execPath, [launchScript], {
471
+ detached: true,
472
+ stdio: 'ignore',
473
+ cwd: PLUGIN_ROOT,
474
+ windowsHide: true,
475
+ env: {
476
+ ...process.env,
477
+ CLAUDE_PLUGIN_ROOT: PLUGIN_ROOT,
478
+ CLAUDE_PLUGIN_DATA: PLUGIN_DATA,
479
+ },
480
+ });
481
+ child.unref();
482
+ }
483
+
484
+ process.exit(failCount > 0 ? 1 : 0);