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,213 @@
1
+ /**
2
+ * IMPORTANT — cwd model role:
3
+ * pwd() resolves the user's working directory for RELATIVE PATH RESOLUTION only.
4
+ * It is NOT a sandbox boundary. Sandbox decisions are governed by Claude Code's
5
+ * settings.json permissions govern sandbox decisions.
6
+ */
7
+
8
+ /**
9
+ * user-cwd.mjs — shared helper to resolve the user's working directory
10
+ * from the persisted user-cwd.txt sentinel file, with an optional
11
+ * session-cwd override (process.env.MIXDOG_SESSION_CWD) that takes
12
+ * precedence when set.
13
+ *
14
+ * Single-source-of-truth model:
15
+ * - captureOriginalUserCwd() reads MIXDOG_SESSION_CWD first (if set
16
+ * to a valid directory), otherwise user-cwd.txt fresh on every
17
+ * call. No in-memory freeze.
18
+ * - rawUserCwd() reads ONLY user-cwd.txt (no env consult) — exposed
19
+ * for the cwd-tool auto-init path so the env-var fallback cannot
20
+ * become self-referential.
21
+ * - AsyncLocalStorage override (runWithCwdOverride) isolates concurrent worker cwds.
22
+ * - pwd() = override ?? originalCwd. Hot lookups short-circuit on the
23
+ * override, so the no-override fallback path is cold and per-call
24
+ * disk reads of user-cwd.txt are negligible.
25
+ */
26
+
27
+ import { AsyncLocalStorage } from 'async_hooks'
28
+ import { readFileSync, statSync, writeFileSync } from 'fs'
29
+ import { join, resolve } from 'path'
30
+ import { homedir } from 'os'
31
+
32
+ const _cwdOverride = new AsyncLocalStorage()
33
+
34
+ // process.cwd() is the server's LAUNCH directory. In daemon mode that IS
35
+ // CLAUDE_PLUGIN_ROOT (the plugin install/cache root), so using it as a
36
+ // relative-path base silently resolves into the DEPLOYED plugin copy instead
37
+ // of the user's working tree — the exact cause of stale reads in a worker that
38
+ // inherited no explicit cwd. Treat that one case as "no usable cwd" and fall
39
+ // back to the home dir: a missing session signal then surfaces as ENOENT
40
+ // (absolute paths required) rather than a silent stale read. Invariant check
41
+ // (exact path equality with the known root), not a path-substring heuristic.
42
+ export function _safeProcessCwd() {
43
+ const cwd = process.cwd()
44
+ const pluginRoot = process.env.CLAUDE_PLUGIN_ROOT
45
+ if (pluginRoot) {
46
+ try { if (resolve(cwd) === resolve(pluginRoot)) return homedir() } catch { /* fall through to cwd */ }
47
+ }
48
+ return cwd
49
+ }
50
+
51
+ // Hook payloads can deliver POSIX paths on Windows (e.g. `/c/Project`); Node's
52
+ // path.resolve does not map MSYS-style drive prefixes, so the value must be
53
+ // rewritten to the platform-native shape before path resolution.
54
+ function _normalizePlatformCwd(p) {
55
+ if (!p || typeof p !== 'string') return p
56
+ if (process.platform !== 'win32') return resolve(p)
57
+ const m = p.match(/^[\/\\]([a-zA-Z])[\/\\](.*)$/)
58
+ const native = m ? `${m[1].toUpperCase()}:\\${m[2].replace(/\//g, '\\')}` : p
59
+ return resolve(native)
60
+ }
61
+
62
+ /**
63
+ * Resolve the session cwd from EXPLICIT signals only:
64
+ * 1. process.env.MIXDOG_SESSION_CWD — session-level override set via
65
+ * the `cwd` MCP tool. Honoured only when non-empty AND the resolved
66
+ * directory actually exists.
67
+ * 2. user-cwd.txt — single source of truth maintained by claude-code
68
+ * (rewritten at every session start).
69
+ * Returns null when neither is available.
70
+ *
71
+ * Unlike captureOriginalUserCwd()/pwd(), this NEVER falls back to
72
+ * process.cwd(): the server's launch directory is not a session signal,
73
+ * and letting it leak into PROJECT CLASSIFICATION (resolveProjectScope)
74
+ * would misclassify rows under the service/plugin cwd. Use this for
75
+ * project_id resolution; use pwd() for relative-path resolution.
76
+ */
77
+ export function explicitSessionCwd() {
78
+ const sessionRaw = process.env.MIXDOG_SESSION_CWD
79
+ if (typeof sessionRaw === 'string' && sessionRaw.length > 0) {
80
+ const normalized = _normalizePlatformCwd(sessionRaw)
81
+ if (normalized) {
82
+ try {
83
+ const st = statSync(normalized)
84
+ if (st.isDirectory()) return normalized
85
+ } catch { /* fall through to user-cwd.txt */ }
86
+ }
87
+ }
88
+ try {
89
+ const txt = readFileSync(join(process.env.CLAUDE_PLUGIN_DATA || '', 'user-cwd.txt'), 'utf8').trim()
90
+ return (txt && _normalizePlatformCwd(txt)) || null
91
+ } catch {
92
+ return null
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Resolve the Claude Code SESSION ENTRY root from CLAUDE_PROJECT_DIR — the
98
+ * directory the user launched Claude Code in, injected by the host on every
99
+ * run. This is an explicit host-provided invariant (not a heuristic guess),
100
+ * so it is the correct relative-path base when no session cwd has been set
101
+ * yet AND user-cwd.txt is unwritten (e.g. a hook fired with an empty
102
+ * _event.cwd). Returns null when the var is absent or not a live directory,
103
+ * so callers chain it ahead of the process.cwd() last resort.
104
+ */
105
+ function startRootCwd() {
106
+ const dir = process.env.CLAUDE_PROJECT_DIR
107
+ if (typeof dir === 'string' && dir.length > 0) {
108
+ const normalized = _normalizePlatformCwd(dir)
109
+ if (normalized) {
110
+ try {
111
+ if (statSync(normalized).isDirectory()) return normalized
112
+ } catch { /* not a live directory — fall through */ }
113
+ }
114
+ }
115
+ return null
116
+ }
117
+
118
+ /**
119
+ * Resolve the user's current working directory for RELATIVE PATH
120
+ * RESOLUTION. Same explicit precedence as explicitSessionCwd(), then the
121
+ * session-entry root (CLAUDE_PROJECT_DIR), with process.cwd() as the final
122
+ * fallback when no explicit session cwd exists.
123
+ *
124
+ * Read fresh on every call: hot lookups inside a worker are short-
125
+ * circuited by runWithCwdOverride AsyncLocalStorage long before reaching
126
+ * this function, so the no-override fallback path is cold and per-call
127
+ * disk reads are negligible.
128
+ */
129
+ export function captureOriginalUserCwd() {
130
+ return explicitSessionCwd() ?? startRootCwd() ?? _safeProcessCwd()
131
+ }
132
+
133
+ /**
134
+ * Read the user-cwd.txt sentinel directly, with NO env-var consult.
135
+ * Used by the cwd-tool auto-init path to avoid self-reference when
136
+ * deciding whether to seed MIXDOG_SESSION_CWD from disk.
137
+ */
138
+ export function rawUserCwd() {
139
+ try {
140
+ const txt = readFileSync(join(process.env.CLAUDE_PLUGIN_DATA || '', 'user-cwd.txt'), 'utf8').trim()
141
+ return _normalizePlatformCwd(txt) || startRootCwd() || _safeProcessCwd()
142
+ } catch {
143
+ return startRootCwd() ?? _safeProcessCwd()
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Path of the persisted last-session-cwd sentinel, KEYED by the supervisor
149
+ * (run-mcp) PID injected as MIXDOG_SUPERVISOR_PID. The supervisor is one
150
+ * per terminal/MCP client and is preserved across a dev-sync child restart
151
+ * (only the child is killed + respawned), so its PID is a stable, per-
152
+ * terminal key that survives the restart. Keying by it makes the restore
153
+ * multi-terminal safe: terminal A's `cwd set` writes session-cwd-<pidA>.txt
154
+ * and terminal B writes session-cwd-<pidB>.txt, so a restart in one terminal
155
+ * can never restore the other terminal's cwd. When no supervisor PID is
156
+ * present (direct launch with no respawn lifecycle) a single 'solo' file is
157
+ * used — there is no cross-restart key in that mode, matching the absence of
158
+ * a respawning supervisor.
159
+ */
160
+ function _lastSessionCwdFile(keyPid) {
161
+ // Explicit keyPid (a connection's leadPid in daemon mode) wins; otherwise
162
+ // fall back to this process's MIXDOG_SUPERVISOR_PID. Under the shared daemon
163
+ // a single process serves N terminals, so keying writes by the per-connection
164
+ // leadPid is what keeps one terminal's `cwd set` out of another's sentinel.
165
+ const raw = (keyPid != null && keyPid !== '') ? String(keyPid) : process.env.MIXDOG_SUPERVISOR_PID
166
+ const key = (typeof raw === 'string' && /^\d+$/.test(raw)) ? raw : 'solo'
167
+ return join(process.env.CLAUDE_PLUGIN_DATA || '', `session-cwd-${key}.txt`)
168
+ }
169
+
170
+ /**
171
+ * Best-effort persist the last session cwd to disk (keyed per supervisor —
172
+ * see _lastSessionCwdFile) so the respawned child of the SAME terminal can
173
+ * restore it. Errors are swallowed — a convenience signal, not a contract.
174
+ */
175
+ export function writeLastSessionCwd(cwd, keyPid) {
176
+ try {
177
+ writeFileSync(_lastSessionCwdFile(keyPid), String(cwd))
178
+ } catch { /* best-effort */ }
179
+ }
180
+
181
+ /**
182
+ * Read the persisted last session cwd for THIS terminal (keyed per supervisor
183
+ * PID). Returns the normalized path only when the directory still exists;
184
+ * otherwise null. Consulted by the boot-time cwd auto-init (server-main.mjs)
185
+ * ahead of the user-cwd.txt seed so the last `cwd set` survives a dev-sync
186
+ * child restart that dropped MIXDOG_SESSION_CWD.
187
+ */
188
+ export function readLastSessionCwd(keyPid) {
189
+ try {
190
+ const content = readFileSync(_lastSessionCwdFile(keyPid), 'utf8')
191
+ const normalized = _normalizePlatformCwd(content.trim())
192
+ if (normalized && statSync(normalized).isDirectory()) return normalized
193
+ return null
194
+ } catch {
195
+ return null
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Run fn inside an async context where pwd() returns cwd.
201
+ * All descendant async calls within fn see cwd as their working directory.
202
+ */
203
+ export function runWithCwdOverride(cwd, fn) {
204
+ return _cwdOverride.run(cwd, fn)
205
+ }
206
+
207
+ /**
208
+ * Current effective working directory:
209
+ * override set by runWithCwdOverride (innermost wins) ?? original user cwd.
210
+ */
211
+ export function pwd() {
212
+ return _cwdOverride.getStore() ?? captureOriginalUserCwd()
213
+ }
@@ -0,0 +1,238 @@
1
+ import {
2
+ copyFileSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ rmSync,
8
+ statSync,
9
+ writeFileSync,
10
+ } from 'fs';
11
+ import { dirname, join, resolve } from 'path';
12
+ import { homedir } from 'os';
13
+ import { createHash } from 'crypto';
14
+
15
+ export function getBackupRoot() {
16
+ return process.env.MIXDOG_USER_DATA_BACKUP_ROOT
17
+ || join(homedir(), '.claude', 'backups', 'mixdog-user-data');
18
+ }
19
+ const RECOVERY_NOTICE = 'RECOVERY-REQUIRED.txt';
20
+
21
+ const USER_DATA_FILES = [
22
+ 'mixdog-config.json',
23
+ 'user-workflow.json',
24
+ 'user-workflow.md',
25
+ 'history/user.md',
26
+ 'history/bot.md',
27
+ ];
28
+
29
+ const USER_DATA_DIRS = [
30
+ 'schedules',
31
+ 'webhooks',
32
+ 'roles',
33
+ 'workflows',
34
+ ];
35
+
36
+ function stamp() {
37
+ return new Date().toISOString().replace(/[:.]/g, '-');
38
+ }
39
+
40
+ function safeReason(reason) {
41
+ return String(reason || 'snapshot').replace(/[^a-z0-9_.-]+/gi, '-').slice(0, 48) || 'snapshot';
42
+ }
43
+
44
+ function initMarkerPath(dataDir) {
45
+ const id = createHash('sha256').update(String(dataDir || 'unknown')).digest('hex').slice(0, 16);
46
+ return join(getBackupRoot(), `.initialized-${id}.json`);
47
+ }
48
+
49
+ function isPlainObject(value) {
50
+ return !!value && typeof value === 'object' && !Array.isArray(value);
51
+ }
52
+
53
+ export function hasUserDataInitMarker(dataDir) {
54
+ return existsSync(initMarkerPath(dataDir));
55
+ }
56
+
57
+ /** Skip single-section wipe remnants (e.g. `{ search: … }` only). */
58
+ export function isStructurallyCompleteMixdogConfigBackup(parsed) {
59
+ if (!isPlainObject(parsed)) return false;
60
+ if (Object.keys(parsed).length <= 1) return false;
61
+ if (!parsed.agent && !parsed.channels) return false;
62
+ return true;
63
+ }
64
+
65
+ /**
66
+ * Newest backup first: return the first structurally complete mixdog-config.json
67
+ * (skips degenerate single-section snapshots from a prior failed RMW).
68
+ */
69
+ export function loadLatestMixdogConfigFromBackup(_dataDir) {
70
+ const root = getBackupRoot();
71
+ let entries = [];
72
+ try {
73
+ entries = readdirSync(root, { withFileTypes: true })
74
+ .filter((entry) => entry.isDirectory())
75
+ .map((entry) => entry.name)
76
+ .sort()
77
+ .reverse();
78
+ } catch {
79
+ return null;
80
+ }
81
+ for (const name of entries) {
82
+ const cfgPath = join(root, name, 'mixdog-config.json');
83
+ if (!existsSync(cfgPath)) continue;
84
+ try {
85
+ const parsed = JSON.parse(readFileSync(cfgPath, 'utf8'));
86
+ if (isStructurallyCompleteMixdogConfigBackup(parsed)) return parsed;
87
+ } catch {
88
+ continue;
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+
94
+ function copyTree(src, dst, copied) {
95
+ const st = statSync(src);
96
+ if (st.isDirectory()) {
97
+ for (const name of readdirSync(src)) {
98
+ copyTree(join(src, name), join(dst, name), copied);
99
+ }
100
+ return;
101
+ }
102
+ if (!st.isFile()) return;
103
+ mkdirSync(dirname(dst), { recursive: true });
104
+ copyFileSync(src, dst);
105
+ copied.push(dst);
106
+ }
107
+
108
+ function pruneBackups(keep = 40) {
109
+ let entries = [];
110
+ try {
111
+ entries = readdirSync(getBackupRoot(), { withFileTypes: true })
112
+ .filter((entry) => entry.isDirectory())
113
+ .map((entry) => entry.name)
114
+ .sort()
115
+ .reverse();
116
+ } catch {
117
+ return;
118
+ }
119
+ for (const name of entries.slice(keep)) {
120
+ try { rmSync(join(getBackupRoot(), name), { recursive: true, force: true }); } catch {}
121
+ }
122
+ }
123
+
124
+ export function markUserDataInitialized(dataDir) {
125
+ try {
126
+ mkdirSync(getBackupRoot(), { recursive: true });
127
+ writeFileSync(initMarkerPath(dataDir), JSON.stringify({
128
+ dataDir,
129
+ updatedAt: new Date().toISOString(),
130
+ }, null, 2) + '\n', 'utf8');
131
+ } catch {}
132
+ }
133
+
134
+ export function backupUserData(dataDir, reason = 'snapshot') {
135
+ if (process.env.MIXDOG_SKIP_USER_DATA_BACKUP === '1' || process.env.MIXDOG_SKIP_USER_DATA_BACKUP === 'true') {
136
+ return { dir: null, copied: [] };
137
+ }
138
+ if (!dataDir || !existsSync(dataDir)) return { dir: null, copied: [] };
139
+ const backupDir = join(getBackupRoot(), `${stamp()}-${safeReason(reason)}`);
140
+ const copied = [];
141
+ for (const rel of USER_DATA_FILES) {
142
+ const src = join(dataDir, rel);
143
+ if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
144
+ }
145
+ for (const rel of USER_DATA_DIRS) {
146
+ const src = join(dataDir, rel);
147
+ if (existsSync(src)) copyTree(src, join(backupDir, rel), copied);
148
+ }
149
+ if (copied.length > 0) {
150
+ markUserDataInitialized(dataDir);
151
+ pruneBackups();
152
+ process.stderr.write(`[user-data-backup] ${reason}: copied ${copied.length} file(s) to ${backupDir}\n`);
153
+ }
154
+ return { dir: copied.length > 0 ? backupDir : null, copied };
155
+ }
156
+
157
+ export function shouldSeedMissingUserData(dataDir, rel) {
158
+ if (!dataDir) return true;
159
+ if (existsSync(join(dataDir, rel))) {
160
+ markUserDataInitialized(dataDir);
161
+ return false;
162
+ }
163
+ const markerPath = initMarkerPath(dataDir);
164
+ if (!existsSync(markerPath)) return true;
165
+ try {
166
+ mkdirSync(dataDir, { recursive: true });
167
+ writeFileSync(join(dataDir, RECOVERY_NOTICE), [
168
+ 'Mixdog refused to recreate missing user data from defaults.',
169
+ '',
170
+ `Missing file: ${rel}`,
171
+ `Data dir: ${dataDir}`,
172
+ `Backup root: ${getBackupRoot()}`,
173
+ '',
174
+ 'Restore the missing file from backup, or delete the backup marker',
175
+ markerPath,
176
+ 'only if this is an intentional fresh reset.',
177
+ '',
178
+ ].join('\n'), 'utf8');
179
+ } catch {}
180
+ process.stderr.write(`[seed-guard] refused default seed for missing ${rel}; restore from ${getBackupRoot()} or intentionally reset marker\n`);
181
+ return false;
182
+ }
183
+
184
+ // Names that mark a directory as the user-data ROOT. A package-manager run
185
+ // (bun install) or recursive delete targeting any directory that IS the data
186
+ // root, or that contains any of these, would wipe user state — so refuse.
187
+ const USER_DATA_SENTINELS = [
188
+ 'mixdog-config.json',
189
+ 'user-workflow.json',
190
+ 'user-workflow.md',
191
+ 'roles',
192
+ 'schedules',
193
+ 'webhooks',
194
+ ];
195
+
196
+ // Subdir name this code OWNS and may freely create/install/delete inside.
197
+ const OWNED_SUBDIR = '.deps';
198
+
199
+ // On Windows the filesystem is case-insensitive, so path comparisons must be
200
+ // case-folded to stop variants like '.../Data/.deps' from bypassing the
201
+ // target===root / dirname===root equality checks.
202
+ const CASE_INSENSITIVE = process.platform === 'win32';
203
+
204
+ function normCase(p) {
205
+ return CASE_INSENSITIVE ? String(p).toLowerCase() : String(p);
206
+ }
207
+
208
+ /**
209
+ * Hard guard: throws unless `targetDir` is EXACTLY the owned subdir directly
210
+ * under the user-data root (`<dataDir>/<ownedSubdir>`). Every other path under
211
+ * the data root is refused — including the root itself, sentinel dirs, and
212
+ * arbitrary subdirs. Call BEFORE any `bun install` / recursive delete.
213
+ *
214
+ * @param {string} targetDir directory a destructive op is about to touch
215
+ * @param {string} dataDir the user-data root to protect
216
+ * @param {string} [op] label for the error message
217
+ * @param {string} [ownedSubdir] the single owned subdir name (default '.deps')
218
+ * @returns {string} the resolved owned path (only on allow)
219
+ */
220
+ export function assertSafeOwnedDir(targetDir, dataDir, op = 'destructive op', ownedSubdir = OWNED_SUBDIR) {
221
+ if (!targetDir) throw new Error(`[data-guard] ${op} refused: empty target dir`);
222
+ if (!dataDir) throw new Error(`[data-guard] ${op} refused: empty data dir`);
223
+
224
+ // STRICT WHITELIST: the ONLY path this code may touch is the exact owned
225
+ // subdir directly under the data root (`<dataDir>/<ownedSubdir>`). Every
226
+ // other path — under the data root OR anywhere else on disk — is refused.
227
+ const target = resolve(targetDir);
228
+ const allowed = resolve(join(dataDir, ownedSubdir));
229
+
230
+ if (normCase(target) === normCase(allowed)) {
231
+ return target;
232
+ }
233
+
234
+ throw new Error(
235
+ `[data-guard] ${op} refused: ${target} is not the owned dir. ` +
236
+ `Only the exact owned subdir (${allowed}) may be operated on.`
237
+ );
238
+ }