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,396 @@
1
+ /**
2
+ * supervisor-pg — PG child process lifecycle wired into the mixdog supervisor.
3
+ *
4
+ * Public API:
5
+ * ensurePgInstance(dataDir) → Promise<{ host, port, runtimeDir, pgdataDir }>
6
+ * stopPgForShutdown() → Promise<void> — call from server-main.mjs shutdown()
7
+ *
8
+ * Depends on Track A's pg-process.mjs for startPg / stopPg / healthcheckPg.
9
+ * Lazy-imported so this module loads cleanly before Track A lands.
10
+ *
11
+ * active-instance.json additions:
12
+ * pg_port? number — TCP port PG is listening on
13
+ * pg_started_at? number — epoch ms when PG was last started
14
+ * pg_pgdata? string — absolute path to the pgdata directory
15
+ * pg_runtime_dir? string — absolute path to the pg runtime binaries dir
16
+ */
17
+
18
+ import { createServer } from 'node:net';
19
+ import {
20
+ unlinkSync, readFileSync, writeFileSync,
21
+ renameSync, statSync, mkdirSync,
22
+ } from 'node:fs';
23
+ import { join, resolve } from 'node:path';
24
+ import { tmpdir } from 'node:os';
25
+ import { writeJsonAtomicSync } from '../../../shared/atomic-file.mjs';
26
+
27
+ // ── pg-process interface (Track A) ───────────────────────────────────────────
28
+ // Dynamic import so this module loads even before Track A's file exists.
29
+ let _pgProc = null;
30
+ async function _getPgProc() {
31
+ if (_pgProc) return _pgProc;
32
+ // import.meta.url is in src/memory/lib/pg/ — process.mjs lives alongside.
33
+ const mod = await import('./process.mjs');
34
+ _pgProc = {
35
+ startPg: mod.startPg,
36
+ stopPg: mod.stopPg,
37
+ healthcheckPg: mod.healthcheckPg,
38
+ reconcileConfV2: mod.reconcileConfV2,
39
+ };
40
+ return _pgProc;
41
+ }
42
+
43
+ // ── In-process state ─────────────────────────────────────────────────────────
44
+ /** @type {{ port: number, pgdata: string, runtimeDir: string, proc: unknown } | null} */
45
+ let _live = null;
46
+ /** Dedup: one ensure coroutine at a time. */
47
+ let _ensureInFlight = null;
48
+ /** Per-process flag — postgresql.conf reconcile attempted once per supervisor. */
49
+ let _v2ReconcileTried = false;
50
+
51
+ // ── Constants ────────────────────────────────────────────────────────────────
52
+ const PG_PORT_MIN = 55432;
53
+ const PG_PORT_MAX = 55632;
54
+ const PG_LOG_MAX_BYTES = 10 * 1024 * 1024; // 10 MB
55
+ const SPAWN_LOCK_NAME = 'pg-spawn.lock';
56
+ const LOCK_WAIT_MS = 30_000;
57
+ const LOCK_POLL_MS = 100;
58
+ const LOCK_WAIT_CODES = new Set(['EEXIST', 'EPERM', 'EACCES', 'EBUSY']);
59
+
60
+ // ── File lock (O_EXCL pattern from dispatch-persist.mjs / run-mcp.mjs) ───────
61
+
62
+ /**
63
+ * Acquire `${dataDir}/pg-spawn.lock`.
64
+ * Returns the lock-file path on success, or null on timeout (caller proceeds
65
+ * best-effort — worst case: two supervisors race initdb on the same pgdata,
66
+ * but the second will fail pg_ctl start gracefully and healthcheck will
67
+ * converge on the first's port).
68
+ */
69
+ async function acquireSpawnLock(dataDir) {
70
+ const lp = join(dataDir, SPAWN_LOCK_NAME);
71
+ const deadline = Date.now() + LOCK_WAIT_MS;
72
+ const body = JSON.stringify({ pid: process.pid, startedAt: Date.now() });
73
+ for (;;) {
74
+ try {
75
+ // 'wx' = O_CREAT | O_EXCL — atomic, fails with EEXIST if held.
76
+ writeFileSync(lp, body, { flag: 'wx' });
77
+ return lp;
78
+ } catch (err) {
79
+ if (!LOCK_WAIT_CODES.has(err?.code)) {
80
+ process.stderr.write(`[supervisor-pg] spawn lock error: ${err?.code || err?.message}\n`);
81
+ return null;
82
+ }
83
+ // Dead-holder detection: same as run-mcp.mjs acquireLock().
84
+ try {
85
+ const holder = JSON.parse(readFileSync(lp, 'utf8'));
86
+ if (holder?.pid) {
87
+ try { process.kill(holder.pid, 0); }
88
+ catch (ke) {
89
+ if (ke.code === 'ESRCH') {
90
+ try { unlinkSync(lp); } catch {}
91
+ continue; // retry immediately after removing stale lock
92
+ }
93
+ }
94
+ }
95
+ } catch { /* unreadable — fall through and wait */ }
96
+ if (Date.now() >= deadline) {
97
+ throw new Error(`[supervisor-pg] spawn-lock acquire timeout (concurrent supervisor risk)`);
98
+ }
99
+ await new Promise(r => setTimeout(r, LOCK_POLL_MS));
100
+ }
101
+ }
102
+ }
103
+
104
+ function releaseSpawnLock(lp) {
105
+ if (!lp) return;
106
+ try { unlinkSync(lp); } catch {}
107
+ }
108
+
109
+ // ── Port allocation ──────────────────────────────────────────────────────────
110
+
111
+ function _probePort(port) {
112
+ return new Promise(resolve => {
113
+ const srv = createServer();
114
+ srv.once('error', () => resolve(false));
115
+ srv.once('listening', () => srv.close(() => resolve(true)));
116
+ srv.listen(port, '127.0.0.1');
117
+ });
118
+ }
119
+
120
+ async function allocatePort() {
121
+ for (let p = PG_PORT_MIN; p <= PG_PORT_MAX; p++) {
122
+ if (await _probePort(p)) return p;
123
+ }
124
+ throw new Error(`[supervisor-pg] no free TCP port in range ${PG_PORT_MIN}–${PG_PORT_MAX}`);
125
+ }
126
+
127
+ // ── Log rotation ─────────────────────────────────────────────────────────────
128
+ // Keep at most 1 archive (pg.log.1). Rotate at PG_LOG_MAX_BYTES.
129
+
130
+ function rotateLogIfNeeded(logPath) {
131
+ try {
132
+ const st = statSync(logPath);
133
+ if (st.size > PG_LOG_MAX_BYTES) {
134
+ const archive = logPath + '.1';
135
+ try { unlinkSync(archive); } catch {}
136
+ renameSync(logPath, archive);
137
+ }
138
+ } catch { /* log does not exist yet — nothing to rotate */ }
139
+ }
140
+
141
+ // ── active-instance.json patch ───────────────────────────────────────────────
142
+ // Atomic read-modify-write using the same tmp+rename pattern as server.mjs.
143
+ // Lives in MIXDOG_RUNTIME_ROOT or os.tmpdir()/mixdog/
144
+ // (see src/channels/lib/runtime-paths.mjs).
145
+
146
+ const _RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT
147
+ ? resolve(process.env.MIXDOG_RUNTIME_ROOT)
148
+ : join(tmpdir(), 'mixdog');
149
+ const _ACTIVE_FILE = join(_RUNTIME_ROOT, 'active-instance.json');
150
+
151
+ function patchActiveInstance(fields) {
152
+ try {
153
+ let curRaw = {};
154
+ try { curRaw = JSON.parse(readFileSync(_ACTIVE_FILE, 'utf8')); } catch {}
155
+ // Drop stale fields (pid/startedAt) written by older server versions.
156
+ const { pid: _legacyPid, startedAt: _legacyStartedAt, ...cur } = curRaw ?? {};
157
+ // Omit null-valued fields (clean removal when pg is stopped).
158
+ const merged = { ...cur, updatedAt: Date.now() };
159
+ for (const [k, v] of Object.entries(fields)) {
160
+ if (v == null) delete merged[k];
161
+ else merged[k] = v;
162
+ }
163
+ writeJsonAtomicSync(_ACTIVE_FILE, merged, { compact: true, lock: true, fsyncDir: true });
164
+ } catch (e) {
165
+ process.stderr.write(`[supervisor-pg] patchActiveInstance failed: ${e?.message}\n`);
166
+ }
167
+ }
168
+
169
+ // ── postmaster.pid helpers ───────────────────────────────────────────────────
170
+
171
+ function readPostmasterPid(pgdata) {
172
+ try {
173
+ const raw = readFileSync(join(pgdata, 'postmaster.pid'), 'utf8');
174
+ const pid = parseInt(raw.split('\n')[0], 10);
175
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
176
+ } catch { return null; }
177
+ }
178
+
179
+ function isPidAlive(pid) {
180
+ try { process.kill(pid, 0); return true; }
181
+ catch (e) {
182
+ // EPERM: process exists but we lack permission → alive.
183
+ // ESRCH: no such process → dead.
184
+ return e.code === 'EPERM';
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Best-effort process-name check: confirms the pid's comm name contains 'postgres'.
190
+ * Falls back to true (alive) when the name cannot be determined.
191
+ */
192
+ async function isPostgresPid(pid) {
193
+ try {
194
+ if (process.platform === 'linux') {
195
+ const { readFileSync: rfs } = await import('node:fs');
196
+ const comm = rfs(`/proc/${pid}/comm`, 'utf8').trim();
197
+ return comm.includes('postgres');
198
+ }
199
+ if (process.platform === 'darwin') {
200
+ const { spawnSync } = await import('node:child_process');
201
+ const r = spawnSync('ps', ['-o', 'comm=', '-p', String(pid)], { encoding: 'utf8', windowsHide: true });
202
+ if (r.status === 0) return (r.stdout || '').trim().includes('postgres');
203
+ return true;
204
+ }
205
+ if (process.platform === 'win32') {
206
+ const { spawnSync } = await import('node:child_process');
207
+ const r = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf8', windowsHide: true });
208
+ if (r.status === 0) return (r.stdout || '').toLowerCase().includes('postgres');
209
+ return true;
210
+ }
211
+ } catch { /* cannot read — fall back to alive */ }
212
+ return true;
213
+ }
214
+
215
+ async function isPostmasterAlive(pid) {
216
+ if (!isPidAlive(pid)) return false;
217
+ return isPostgresPid(pid);
218
+ }
219
+
220
+ // ── Internal: spawn a fresh PG instance ─────────────────────────────────────
221
+
222
+ async function _startFresh(dataDir, pgdata, port, runtimeDir) {
223
+ const { startPg } = await _getPgProc();
224
+ const logPath = join(dataDir, 'pg.log');
225
+ rotateLogIfNeeded(logPath);
226
+ mkdirSync(pgdata, { recursive: true });
227
+ // Track A's startPg handles initdb (if needed) then pg_ctl start.
228
+ // stdout/stderr are directed to logPath by Track A's implementation.
229
+ const pgdataDir = pgdata;
230
+ const proc = await startPg({ runtimeDir, pgdataDir, port, logPath });
231
+ const actualPort = proc?.port ?? port;
232
+ _live = { port: actualPort, pgdata, runtimeDir, proc };
233
+ patchActiveInstance({
234
+ pg_port: actualPort, pg_started_at: Date.now(),
235
+ pg_pgdata: pgdata, pg_runtime_dir: runtimeDir,
236
+ });
237
+ process.stderr.write(`[supervisor-pg] ${proc?.attached ? 'attached to' : 'started'} PG port=${actualPort} pgdata=${pgdata}\n`);
238
+ return { host: '127.0.0.1', port: actualPort, runtimeDir, pgdataDir };
239
+ }
240
+
241
+ // ── Internal: full ensure logic (runs exclusively via _ensureInFlight) ────────
242
+
243
+ async function _doEnsure(dataDir) {
244
+ const { healthcheckPg, stopPg } = await _getPgProc();
245
+ const pgdata = join(dataDir, 'pgdata');
246
+
247
+ // Resolve runtimeDir via runtime-fetcher (cache-hits immediately when already downloaded).
248
+ const { ensureRuntime } = await import('../runtime-fetcher.mjs');
249
+ const { runtimeDir } = await ensureRuntime(dataDir);
250
+
251
+ // One-shot v2 conf reconcile — idempotent. Covers attach paths (in-process
252
+ // fast path / cross-process active-instance reuse) where startPg is never
253
+ // re-invoked on already-running PG instances.
254
+ if (!_v2ReconcileTried) {
255
+ _v2ReconcileTried = true;
256
+ try {
257
+ const { reconcileConfV2 } = await _getPgProc();
258
+ reconcileConfV2(runtimeDir, pgdata);
259
+ } catch (e) {
260
+ process.stderr.write(`[supervisor-pg] reconcileConfV2 error (non-fatal): ${e?.message}\n`);
261
+ }
262
+ }
263
+
264
+ // ── Fast path: already live in this process ──────────────────────────────
265
+ if (_live) {
266
+ try {
267
+ if (await healthcheckPg({ port: _live.port })) {
268
+ return { host: '127.0.0.1', port: _live.port, runtimeDir: _live.runtimeDir, pgdataDir: _live.pgdata };
269
+ }
270
+ } catch {}
271
+ // healthcheck failed — fall through to recovery under lock
272
+ process.stderr.write(`[supervisor-pg] in-process PG failed healthcheck — recovering\n`);
273
+ _live = null;
274
+ }
275
+
276
+ // ── Acquire spawn lock to serialize initdb races ─────────────────────────
277
+ const lp = await acquireSpawnLock(dataDir);
278
+ try {
279
+ // ── Reuse path: another supervisor already started PG ─────────────────
280
+ let existingPort = null;
281
+ let ai = null;
282
+ try {
283
+ ai = JSON.parse(readFileSync(_ACTIVE_FILE, 'utf8'));
284
+ // Only reuse a recorded instance when it was started for THIS pgdata.
285
+ // active-instance.json is process-global; without matching pg_pgdata a
286
+ // healthy PG serving a different data directory would be reused, binding
287
+ // this dataDir's memory to the wrong cluster. A missing/mismatched
288
+ // pg_pgdata falls through to a fresh start for the requested pgdata.
289
+ if (ai?.pg_port && ai?.pg_pgdata && resolve(ai.pg_pgdata) === resolve(pgdata)) {
290
+ existingPort = ai.pg_port;
291
+ }
292
+ } catch {}
293
+
294
+ if (existingPort) {
295
+ try {
296
+ if (await healthcheckPg({ port: existingPort })) {
297
+ process.stderr.write(`[supervisor-pg] reusing PG on port ${existingPort}\n`);
298
+ const existingRtDir = ai?.pg_runtime_dir ?? runtimeDir;
299
+ _live = { port: existingPort, pgdata, runtimeDir: existingRtDir, proc: null };
300
+ return { host: '127.0.0.1', port: existingPort, runtimeDir: existingRtDir, pgdataDir: pgdata };
301
+ }
302
+ } catch {}
303
+
304
+ // ── Stale detection: pg_port recorded but healthcheck failing ─────────
305
+ process.stderr.write(
306
+ `[supervisor-pg] pg_port=${existingPort} recorded but healthcheck failed — recovering\n`,
307
+ );
308
+ const pmPid = readPostmasterPid(pgdata);
309
+ if (pmPid && await isPostmasterAlive(pmPid)) {
310
+ // postmaster alive but unhealthy: attempt graceful stop first
311
+ process.stderr.write(`[supervisor-pg] postmaster PID ${pmPid} alive — attempting graceful stopPg\n`);
312
+ try { await stopPg({ runtimeDir, pgdataDir: pgdata }); } catch (e) {
313
+ process.stderr.write(`[supervisor-pg] graceful stopPg failed: ${e?.message} — continuing to fresh start\n`);
314
+ }
315
+ } else if (pmPid) {
316
+ // postmaster dead: remove stale postmaster.pid so initdb/start is not blocked
317
+ process.stderr.write(`[supervisor-pg] postmaster PID ${pmPid} dead — removing stale postmaster.pid\n`);
318
+ try { unlinkSync(join(pgdata, 'postmaster.pid')); } catch {}
319
+ }
320
+ // Clear stale pg fields before restart
321
+ patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null });
322
+ }
323
+
324
+ // ── Allocate a fresh port and spawn ───────────────────────────────────
325
+ const port = await allocatePort();
326
+ return await _startFresh(dataDir, pgdata, port, runtimeDir);
327
+ } finally {
328
+ releaseSpawnLock(lp);
329
+ }
330
+ }
331
+
332
+ // ── Public API ───────────────────────────────────────────────────────────────
333
+
334
+ /**
335
+ * Ensure a live PG instance exists for `dataDir`.
336
+ * Lazy: does nothing until first call — sessions that never touch memory pay
337
+ * zero cost.
338
+ *
339
+ * Concurrent calls are deduplicated — exactly one spawn/recover sequence runs
340
+ * at a time; additional callers await the same promise.
341
+ *
342
+ * @param {string} dataDir Plugin data directory (CLAUDE_PLUGIN_DATA).
343
+ * @returns {Promise<{ host: string, port: number, runtimeDir: string, pgdataDir: string }>}
344
+ */
345
+ export function ensurePgInstance(dataDir) {
346
+ if (!_ensureInFlight) {
347
+ _ensureInFlight = _doEnsure(dataDir).finally(() => { _ensureInFlight = null; });
348
+ }
349
+ return _ensureInFlight;
350
+ }
351
+
352
+ /**
353
+ * Graceful PG shutdown — call from server-main.mjs shutdown() after workers
354
+ * have stopped. Sends pg_ctl stop to the pgdata directory; clears pg_port
355
+ * from active-instance.json.
356
+ *
357
+ * On the supervisor detached-killer path (run-mcp.mjs killChild → SIGTERM
358
+ * with no time for graceful stop) this function is never called — that is
359
+ * intentional. The next ensurePgInstance call will detect the stale
360
+ * postmaster.pid via isPidAlive() and recover automatically.
361
+ */
362
+ export async function stopPgForShutdown() {
363
+ if (!_live) {
364
+ // _live may be null if PG was started by another process or adapter call.
365
+ // Attempt graceful stop via active-instance.json.
366
+ let ai = null;
367
+ try { ai = JSON.parse(readFileSync(_ACTIVE_FILE, 'utf8')); } catch {}
368
+ if (!ai?.pg_port || !ai?.pg_pgdata) return;
369
+ const pgdataDir2 = ai.pg_pgdata;
370
+ const runtimeDir2 = ai.pg_runtime_dir;
371
+ if (!runtimeDir2) {
372
+ process.stderr.write(`[supervisor-pg] stopPgForShutdown: pg_runtime_dir missing from active-instance.json — skipping\n`);
373
+ return;
374
+ }
375
+ try {
376
+ const { stopPg } = await _getPgProc();
377
+ await stopPg({ runtimeDir: runtimeDir2, pgdataDir: pgdataDir2 });
378
+ process.stderr.write(`[supervisor-pg] PG stopped (via active-instance.json) on shutdown\n`);
379
+ } catch (e) {
380
+ process.stderr.write(`[supervisor-pg] stopPg error on shutdown (no _live): ${e?.message}\n`);
381
+ }
382
+ patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null });
383
+ return;
384
+ }
385
+ const snap = _live;
386
+ _live = null;
387
+ _ensureInFlight = null;
388
+ try {
389
+ const { stopPg } = await _getPgProc();
390
+ await stopPg({ runtimeDir: snap.runtimeDir, pgdataDir: snap.pgdata });
391
+ process.stderr.write(`[supervisor-pg] PG stopped gracefully on shutdown\n`);
392
+ } catch (e) {
393
+ process.stderr.write(`[supervisor-pg] stopPg error on shutdown: ${e?.message}\n`);
394
+ }
395
+ patchActiveInstance({ pg_port: null, pg_started_at: null, pg_pgdata: null, pg_runtime_dir: null });
396
+ }
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, dirname, resolve } from 'node:path';
3
+ import { explicitSessionCwd } from '../../shared/user-cwd.mjs';
4
+
5
+ /** @type {Map<string, string|null>} */
6
+ const cache = new Map();
7
+
8
+ /**
9
+ * Walk up from `start`, returning the first directory whose
10
+ * `.mixdog/project.id` file exists. Returns null if no ancestor has one.
11
+ * Skips intermediate `.mixdog` directories that lack `project.id` so an
12
+ * inner empty marker does not mask an outer valid one.
13
+ * @param {string} start - absolute directory path
14
+ * @returns {string|null} absolute path of the containing directory
15
+ */
16
+ function findProjectIdRoot(start) {
17
+ let dir = resolve(start);
18
+ while (true) {
19
+ if (existsSync(join(dir, '.mixdog', 'project.id'))) return dir;
20
+ const parent = dirname(dir);
21
+ if (parent === dir) return null; // filesystem root
22
+ dir = parent;
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Resolve a project_id for the given working directory.
28
+ *
29
+ * Single source: .mixdog/project.id file in cwd or any ancestor directory.
30
+ * Returns the file content (trimmed), or null if no file is found or the
31
+ * content is "common" (case-insensitive).
32
+ *
33
+ * Removed: git origin parsing, gh CLI permission check, owner whitelist
34
+ * branch, lazy .mixdog/project.id write. Those were multi-step heuristics
35
+ * with no objective signal — project membership must be declared explicitly
36
+ * via a .mixdog/project.id file.
37
+ *
38
+ * Result is memoized by the .mixdog root directory path.
39
+ *
40
+ * @param {string} cwd - absolute or relative working directory
41
+ * @returns {string|null}
42
+ */
43
+ export function resolveProjectId(cwd) {
44
+ const absCwd = resolve(cwd);
45
+
46
+ const mixdogRoot = findProjectIdRoot(absCwd);
47
+ if (!mixdogRoot) return null;
48
+
49
+ if (cache.has(mixdogRoot)) return cache.get(mixdogRoot);
50
+
51
+ const idFile = join(mixdogRoot, '.mixdog', 'project.id');
52
+ let content;
53
+ try {
54
+ content = readFileSync(idFile, 'utf8').trim();
55
+ } catch {
56
+ // TOCTOU: the file vanished between findProjectIdRoot's existsSync and
57
+ // this read. Treat as no marker; do not cache so a transient miss can
58
+ // recover on a later call.
59
+ return null;
60
+ }
61
+ // "common" (case-insensitive) → forced COMMON
62
+ if (content.toLowerCase() === 'common' || !content) {
63
+ cache.set(mixdogRoot, null);
64
+ return null;
65
+ }
66
+
67
+ cache.set(mixdogRoot, content);
68
+ return content;
69
+ }
70
+
71
+ /**
72
+ * Resolve a project_id for PROJECT CLASSIFICATION.
73
+ *
74
+ * Uses the explicitly-supplied cwd when provided; otherwise the explicit
75
+ * session cwd (MIXDOG_SESSION_CWD / user-cwd.txt) via explicitSessionCwd().
76
+ * Returns null (-> COMMON scope) when no explicit cwd exists. Never consults
77
+ * process.cwd(): the server's launch dir is not a project signal and would
78
+ * misclassify rows stored under the service/plugin cwd.
79
+ *
80
+ * @param {string|null|undefined} explicitCwd - caller-supplied cwd, if any
81
+ * @returns {string|null}
82
+ */
83
+ export function resolveProjectScope(explicitCwd) {
84
+ const cwd = (typeof explicitCwd === 'string' && explicitCwd) ? explicitCwd : explicitSessionCwd();
85
+ return cwd ? resolveProjectId(cwd) : null;
86
+ }