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,392 @@
1
+ // Smoke test: builtin.mjs public utility helpers (path normalize / shape
2
+ // coercion / cache key builders). Edit-input normalization helpers are
3
+ // covered separately in edit-normalize-smoke.mjs; this file targets the
4
+ // rest of the cross-tool surface so a regression there doesn't slip
5
+ // through CI just because edit invariants still pass.
6
+
7
+ import { tmpdir } from 'node:os';
8
+ import { dirname, join } from 'node:path';
9
+ import { fileURLToPath } from 'node:url';
10
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
11
+
12
+ const ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
13
+ process.env.CLAUDE_PLUGIN_ROOT ||= ROOT;
14
+ process.env.CLAUDE_PLUGIN_DATA ||= join(tmpdir(), `mixdog-builtin-utils-smoke-data-${process.pid}`);
15
+ process.env.MIXDOG_HINT_LEVEL ||= 'normal';
16
+
17
+ const {
18
+ posixPathToWindowsPath,
19
+ normalizeInputPath,
20
+ normalizeOutputPath,
21
+ coerceShapeFlex,
22
+ canonicalizeBuiltinToolName,
23
+ executeBuiltinTool,
24
+ formatUnknownBuiltinToolMessage,
25
+ isBuiltinTool,
26
+ } = await import('../src/agent/orchestrator/tools/builtin.mjs');
27
+ const {
28
+ clearReadDedupSession,
29
+ clearScopedToolsForSessionPaths,
30
+ drainPrefetchDiskWrites,
31
+ invalidatePrefetchCache,
32
+ setPrefetchCached,
33
+ setScopedToolCached,
34
+ setReadCached,
35
+ tryPrefetchCached,
36
+ tryScopedToolCached,
37
+ tryReadCached,
38
+ } = await import('../src/agent/orchestrator/session/read-dedup.mjs');
39
+ const {
40
+ TOOL_RESULT_OFFLOAD_PREFIX,
41
+ _internals: offloadInternals,
42
+ compactOffloadedToolResultText,
43
+ } = await import('../src/agent/orchestrator/session/tool-result-offload.mjs');
44
+ const {
45
+ normalizeGrepLine,
46
+ } = await import('../src/agent/orchestrator/tools/builtin/grep-formatting.mjs');
47
+ const {
48
+ _pruneCodeGraphManifestForBudget,
49
+ } = await import('../src/agent/orchestrator/tools/code-graph.mjs');
50
+
51
+ let failures = 0;
52
+ function fail(name, msg) { console.error(`FAIL ${name}: ${msg}`); failures++; }
53
+
54
+ // ── coerceShapeFlex ────────────────────────────────────────────────────────
55
+ // Transports occasionally JSON-stringify array parameters. Helper must:
56
+ // • parse `[a,b]` style strings to arrays,
57
+ // • unwrap `"quoted"` strings to their inner value,
58
+ // • leave plain strings, arrays, and primitives untouched.
59
+ const coerceCases = [
60
+ { name: 'plain-string', input: 'foo', expected: 'foo' },
61
+ { name: 'json-array', input: '["a","b"]', expected: ['a', 'b'] },
62
+ { name: 'json-array-numbers', input: '[1,2,3]', expected: [1, 2, 3] },
63
+ { name: 'quoted-string', input: '"hello"', expected: 'hello' },
64
+ { name: 'actual-array', input: ['a', 'b'], expected: ['a', 'b'] },
65
+ { name: 'empty-string', input: '', expected: '' },
66
+ { name: 'malformed-json', input: '[foo', expected: '[foo' },
67
+ { name: 'undefined', input: undefined, expected: undefined },
68
+ { name: 'number', input: 42, expected: 42 },
69
+ ];
70
+
71
+ const jsonEq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
72
+ for (const { name, input, expected } of coerceCases) {
73
+ const got = coerceShapeFlex(input);
74
+ if (!jsonEq(got, expected)) {
75
+ fail(`coerce:${name}`, `got ${JSON.stringify(got)}, expected ${JSON.stringify(expected)}`);
76
+ }
77
+ }
78
+
79
+ // ── builtin tool name recovery ─────────────────────────────────────────────
80
+ if (canonicalizeBuiltinToolName('grop') !== 'grep') fail('tool-alias:grop', 'expected grop -> grep');
81
+ if (canonicalizeBuiltinToolName('glbo') !== 'glob') fail('tool-alias:glbo', 'expected glbo -> glob');
82
+ if (!isBuiltinTool('grop')) fail('tool-alias:is-builtin', 'expected grop to be treated as builtin alias');
83
+ const unknownToolHint = formatUnknownBuiltinToolMessage('grepp', { pattern: 'needle', path: 'src' }, 'tool');
84
+ if (!unknownToolHint.includes('Did you mean "grep"') || unknownToolHint.includes('next_call:')) {
85
+ fail('tool-alias:unknown-hint', `expected concise grep suggestion, got ${JSON.stringify(unknownToolHint)}`);
86
+ }
87
+
88
+ // ── normalizeInputPath / normalizeOutputPath ───────────────────────────────
89
+ // Path normalization round-trips: Windows forward-slash and backslash
90
+ // representations of the same path collapse to one form; POSIX-style
91
+ // `/c/Users/...` drive paths decode back to Windows drive form.
92
+ // Output normalization is the inverse — the user-facing form mixdog
93
+ // always emits regardless of how it received the path.
94
+ const pathCases = [
95
+ { name: 'windows-backslash', input: 'C:\\Users\\foo\\bar', shouldHaveSlashes: true },
96
+ { name: 'windows-forwardslash', input: 'C:/Users/foo/bar', shouldHaveSlashes: true },
97
+ { name: 'posix-style', input: '/c/Users/foo/bar', shouldHaveSlashes: true },
98
+ { name: 'relative-path', input: 'src/foo.js', shouldHaveSlashes: true },
99
+ { name: 'empty-string', input: '', shouldHaveSlashes: false },
100
+ ];
101
+
102
+ // Looser invariant: idempotent + string-typed. mixdog uses a mix of
103
+ // forward and backward slashes internally; what matters is that the
104
+ // second normalization pass produces the same output as the first.
105
+ for (const { name, input } of pathCases) {
106
+ const a = normalizeInputPath(input);
107
+ if (typeof a !== 'string') { fail(`path-in:${name}`, `expected string, got ${typeof a}`); continue; }
108
+ const b = normalizeInputPath(a);
109
+ if (a !== b) fail(`path-in:${name}:idempotent`, `normalizeInputPath not idempotent: ${JSON.stringify(a)} -> ${JSON.stringify(b)}`);
110
+ }
111
+
112
+ const out1 = normalizeOutputPath('C:\\Users\\foo');
113
+ if (typeof out1 !== 'string') fail('path-out:windows', `expected string, got ${typeof out1}`);
114
+ const out2 = normalizeOutputPath('src/foo.js');
115
+ if (typeof out2 !== 'string') fail('path-out:relative', `expected string, got ${typeof out2}`);
116
+
117
+ // posixPathToWindowsPath: `/c/Users/foo` → drive-letter prefix.
118
+ // Accept either case for the drive letter (POSIX `/c/` doesn't carry case).
119
+ const winFromPosix = posixPathToWindowsPath('/c/Users/foo');
120
+ if (typeof winFromPosix !== 'string') {
121
+ fail('posix-to-win:basic', `expected string, got ${typeof winFromPosix}`);
122
+ } else if (!/^[a-zA-Z]:/.test(winFromPosix)) {
123
+ fail('posix-to-win:basic', `expected drive-letter prefix, got ${JSON.stringify(winFromPosix)}`);
124
+ }
125
+
126
+ // ── read cache key shape ───────────────────────────────────────────────────
127
+ // Object-array reads include line/context/full and file_path aliases in their
128
+ // key. Otherwise read({path:[{path, line:10}]}) could be reused for line:20.
129
+ const cacheWork = join(tmpdir(), `mixdog-builtin-cache-smoke-${process.pid}-${Date.now()}`);
130
+ mkdirSync(cacheWork, { recursive: true });
131
+ try {
132
+ writeFileSync(join(cacheWork, 'cache-key.txt'), 'line-1\nline-2\nline-3\n');
133
+ const sessionId = `builtin-utils-${process.pid}`;
134
+ clearReadDedupSession(sessionId);
135
+ setReadCached({
136
+ sessionId,
137
+ cwd: cacheWork,
138
+ args: { path: [{ file_path: 'cache-key.txt', line: 1, context: 0 }] },
139
+ content: 'line one',
140
+ toolUseId: 'tool-1',
141
+ });
142
+ const hit = tryReadCached({
143
+ sessionId,
144
+ cwd: cacheWork,
145
+ args: { path: [{ path: 'cache-key.txt', line: 1, context: 0 }] },
146
+ });
147
+ if (!hit || hit.content !== 'line one') fail('read-cache:file_path-alias', `expected cache hit, got ${JSON.stringify(hit)}`);
148
+ setReadCached({
149
+ sessionId,
150
+ cwd: cacheWork,
151
+ args: { path: [{ path: 'cache-key.txt', line: 1, context: 0 }] },
152
+ content: 'line one again',
153
+ toolUseId: 'tool-2',
154
+ });
155
+ const miss = tryReadCached({
156
+ sessionId,
157
+ cwd: cacheWork,
158
+ args: { path: [{ path: 'cache-key.txt', line: 2, context: 0 }] },
159
+ });
160
+ if (miss !== null) fail('read-cache:line-partition', `line:2 should not hit line:1 cache, got ${JSON.stringify(miss)}`);
161
+ setReadCached({
162
+ sessionId,
163
+ cwd: cacheWork,
164
+ args: { path: 'cache-key.txt', line: 1, context: 0 },
165
+ content: 'scalar line one',
166
+ toolUseId: 'tool-3',
167
+ });
168
+ const scalarMiss = tryReadCached({
169
+ sessionId,
170
+ cwd: cacheWork,
171
+ args: { path: 'cache-key.txt', line: 2, context: 0 },
172
+ });
173
+ if (scalarMiss !== null) fail('read-cache:scalar-line-partition', `scalar line:2 should not hit line:1 cache, got ${JSON.stringify(scalarMiss)}`);
174
+ clearReadDedupSession(sessionId);
175
+ } finally {
176
+ rmSync(cacheWork, { recursive: true, force: true });
177
+ }
178
+
179
+ // Full-file snapshots must not collapse later line-window reads.
180
+ const readWindowWork = join(tmpdir(), `mixdog-read-window-smoke-${process.pid}-${Date.now()}`);
181
+ mkdirSync(readWindowWork, { recursive: true });
182
+ try {
183
+ writeFileSync(join(readWindowWork, 'window.txt'), Array.from({ length: 20 }, (_, i) => `line-${i + 1}`).join('\n') + '\n');
184
+ const sessionId = `builtin-read-window-${process.pid}`;
185
+ clearReadDedupSession(sessionId);
186
+ await executeBuiltinTool('read', { path: 'window.txt' }, readWindowWork, { readStateScope: sessionId, sessionId });
187
+ const reread = await executeBuiltinTool('read', { path: 'window.txt', line: 10, context: 1 }, readWindowWork, { readStateScope: sessionId, sessionId });
188
+ // Separator-agnostic: the read formatter renders `N<sep>content` (currently
189
+ // `→`). Assert the actual intent — a FRESH line-10±1 window (lines 9-11 of 20)
190
+ // that the prior full-file read did not collapse — not a hardcoded glyph.
191
+ if (!String(reread).includes('line-10') || !String(reread).includes('lines 9-11 of 20')) {
192
+ fail('read-window-after-full', `expected fresh window after full read, got ${JSON.stringify(reread)}`);
193
+ }
194
+ clearReadDedupSession(sessionId);
195
+ } finally {
196
+ rmSync(readWindowWork, { recursive: true, force: true });
197
+ }
198
+
199
+ // ── grep context byte-exactness ───────────────────────────────────────────
200
+ // Windows rg omits the path for a single-file search, so context lines arrive
201
+ // as `N-content`. The formatter must not treat a later colon inside CONTENT as
202
+ // a path delimiter and normalize source backslashes (`\n` -> `/n`).
203
+ const grepContextWork = join(tmpdir(), `mixdog-grep-context-smoke-${process.pid}-${Date.now()}`);
204
+ mkdirSync(grepContextWork, { recursive: true });
205
+ try {
206
+ const fixture = join(grepContextWork, 'context.txt');
207
+ writeFileSync(fixture, 'context literal \\n \\r \\t ? a : b\nneedle\ntrailing\n');
208
+ const numericDir = join(grepContextWork, '12-x');
209
+ mkdirSync(numericDir, { recursive: true });
210
+ const numericFixture = join(numericDir, 'path.txt');
211
+ writeFileSync(numericFixture, 'numeric path literal \\n \\t ? a : b\nnumeric-needle\ntrailing\n');
212
+ const grepOutputs = [
213
+ ['single-file', await executeBuiltinTool(
214
+ 'grep',
215
+ { pattern: 'needle', path: fixture, output_mode: 'content', '-C': 1 },
216
+ grepContextWork,
217
+ { sessionId: `grep-context-file-${process.pid}` },
218
+ )],
219
+ ['absolute-dir', await executeBuiltinTool(
220
+ 'grep',
221
+ { pattern: 'needle', path: grepContextWork, output_mode: 'content', '-C': 1 },
222
+ grepContextWork,
223
+ { sessionId: `grep-context-dir-${process.pid}` },
224
+ )],
225
+ ];
226
+ for (const [caseName, rawOut] of grepOutputs) {
227
+ const out = String(rawOut);
228
+ if (!out.includes('context literal \\n \\r \\t ? a : b')) {
229
+ fail(`grep-context:${caseName}:literal-backslashes`, `expected literal backslash escapes in context, got ${JSON.stringify(out)}`);
230
+ }
231
+ if (out.includes('context literal /n /r /t ? a : b')) {
232
+ fail(`grep-context:${caseName}:no-slash-corruption`, `context backslashes were corrupted, got ${JSON.stringify(out)}`);
233
+ }
234
+ }
235
+ const numericOut = String(await executeBuiltinTool(
236
+ 'grep',
237
+ { pattern: 'numeric-needle', path: '.', output_mode: 'content', '-C': 1 },
238
+ grepContextWork,
239
+ { sessionId: `grep-context-numeric-dir-${process.pid}` },
240
+ ));
241
+ if (!numericOut.includes('12-x/path.txt-1-numeric path literal \\n \\t ? a : b')) {
242
+ fail('grep-context:numeric-leading-path-normalized', `expected digit-hyphen-leading path prefix normalized and content preserved, got ${JSON.stringify(numericOut)}`);
243
+ }
244
+ if (numericOut.includes('numeric path literal /n /t ? a : b')) {
245
+ fail('grep-context:numeric-leading-content-exact', `numeric-leading path context backslashes were corrupted, got ${JSON.stringify(numericOut)}`);
246
+ }
247
+ } finally {
248
+ rmSync(grepContextWork, { recursive: true, force: true });
249
+ }
250
+
251
+ if (process.platform === 'win32') {
252
+ const filenameOmittedContext = String.raw`1-context literal \n \t -5- ? a : b`;
253
+ const normalizedFilenameOmitted = normalizeGrepLine(filenameOmittedContext, false, 'content', true);
254
+ if (normalizedFilenameOmitted !== filenameOmittedContext) {
255
+ fail('grep-context:filename-omitted-exact', `expected byte-exact filename-omitted context, got ${JSON.stringify(normalizedFilenameOmitted)}`);
256
+ }
257
+ const filenamePresentContext = String.raw`12-x\path.txt-5-context literal \n \t ? a : b`;
258
+ const expectedFilenamePresent = String.raw`12-x/path.txt-5-context literal \n \t ? a : b`;
259
+ const normalizedFilenamePresent = normalizeGrepLine(filenamePresentContext);
260
+ if (normalizedFilenamePresent !== expectedFilenamePresent) {
261
+ fail('grep-context:filename-present-exact', `expected exact filename-present context prefix normalization, got ${JSON.stringify(normalizedFilenamePresent)}`);
262
+ }
263
+ }
264
+
265
+ // ── code graph disk cache budget pruning ──────────────────────────────────
266
+ // The code-graph cache is per-cwd, so entry count alone is not enough:
267
+ // a handful of large workspaces can exceed the doctor storage warning.
268
+ // Budget pruning must keep the newest entries that fit the byte cap and
269
+ // drop stale or missing manifest rows.
270
+ const codeGraphCacheWork = join(tmpdir(), `mixdog-code-graph-cache-smoke-${process.pid}-${Date.now()}`);
271
+ mkdirSync(codeGraphCacheWork, { recursive: true });
272
+ try {
273
+ writeFileSync(join(codeGraphCacheWork, 'aaaaaaaaaaaaaaaa.json'), 'a'.repeat(40));
274
+ writeFileSync(join(codeGraphCacheWork, 'bbbbbbbbbbbbbbbb.json'), 'b'.repeat(40));
275
+ writeFileSync(join(codeGraphCacheWork, 'cccccccccccccccc.json'), 'c'.repeat(40));
276
+ const pruned = _pruneCodeGraphManifestForBudget({
277
+ old: { hash: 'aaaaaaaaaaaaaaaa', builtAt: 1 },
278
+ mid: { hash: 'bbbbbbbbbbbbbbbb', builtAt: 2 },
279
+ newest: { hash: 'cccccccccccccccc', builtAt: 3 },
280
+ missing: { hash: 'dddddddddddddddd', builtAt: 4 },
281
+ unsafe: { hash: '../escape', builtAt: 5 },
282
+ }, codeGraphCacheWork, { maxEntries: 2, maxBytes: 70 });
283
+ const keptKeys = Object.keys(pruned.manifest).sort();
284
+ if (!jsonEq(keptKeys, ['newest'])) {
285
+ fail('code-graph-cache:budget-prune', `expected newest only, got ${JSON.stringify(pruned)}`);
286
+ }
287
+ const reasons = new Set((pruned.evicted || []).map((row) => row.reason));
288
+ if (!reasons.has('max-entries') || !reasons.has('max-bytes')) {
289
+ fail('code-graph-cache:prune-reasons', `expected entry and byte prune reasons, got ${JSON.stringify(pruned.evicted)}`);
290
+ }
291
+ } finally {
292
+ rmSync(codeGraphCacheWork, { recursive: true, force: true });
293
+ }
294
+
295
+ // ── scoped cache invalidation ──────────────────────────────────────────────
296
+ // grep/list/glob cache entries are path-root scoped. Editing src/a.txt must
297
+ // evict a prior grep({path:"src"}) result even though the exact touched path
298
+ // differs from the cached root.
299
+ const scopedWork = join(tmpdir(), `mixdog-scoped-cache-smoke-${process.pid}-${Date.now()}`);
300
+ mkdirSync(join(scopedWork, 'src'), { recursive: true });
301
+ try {
302
+ writeFileSync(join(scopedWork, 'src', 'a.txt'), 'needle\n');
303
+ const sessionId = `scoped-utils-${process.pid}`;
304
+ clearReadDedupSession(sessionId);
305
+ setScopedToolCached({
306
+ sessionId,
307
+ toolName: 'grep',
308
+ cwd: scopedWork,
309
+ args: { query: 'needle', root: 'src', mode: 'content' },
310
+ content: 'src/a.txt:1:needle',
311
+ toolUseId: 'grep-1',
312
+ });
313
+ const aliasHit = tryScopedToolCached({
314
+ sessionId,
315
+ toolName: 'grep',
316
+ cwd: scopedWork,
317
+ args: { pattern: 'needle', path: 'src', output_mode: 'content' },
318
+ });
319
+ if (!aliasHit || aliasHit.content !== 'src/a.txt:1:needle') fail('scoped-cache:alias-hit', `expected canonical alias hit, got ${JSON.stringify(aliasHit)}`);
320
+ setScopedToolCached({
321
+ sessionId,
322
+ toolName: 'grep',
323
+ cwd: scopedWork,
324
+ args: { pattern: 'needle', path: 'src', output_mode: 'content' },
325
+ content: 'src/a.txt:1:needle',
326
+ toolUseId: 'grep-2',
327
+ });
328
+ clearScopedToolsForSessionPaths(sessionId, ['src/a.txt'], scopedWork);
329
+ const stale = tryScopedToolCached({
330
+ sessionId,
331
+ toolName: 'grep',
332
+ cwd: scopedWork,
333
+ args: { pattern: 'needle', path: 'src', output_mode: 'content' },
334
+ });
335
+ if (stale !== null) fail('scoped-cache:root-invalidation', `edit under cached root should evict, got ${JSON.stringify(stale)}`);
336
+ clearReadDedupSession(sessionId);
337
+ } finally {
338
+ rmSync(scopedWork, { recursive: true, force: true });
339
+ }
340
+
341
+ // ── prefetch cache invalidation ────────────────────────────────────────────
342
+ const prefetchWork = join(tmpdir(), `mixdog-prefetch-cache-smoke-${process.pid}-${Date.now()}`);
343
+ mkdirSync(prefetchWork, { recursive: true });
344
+ try {
345
+ const filePath = join(prefetchWork, 'prefetch.txt');
346
+ writeFileSync(filePath, 'prefetch source\n');
347
+ invalidatePrefetchCache(filePath, prefetchWork);
348
+ setPrefetchCached(filePath, 'cached prefetch body');
349
+ const hit = tryPrefetchCached(filePath);
350
+ if (!hit || hit.content !== 'cached prefetch body') fail('prefetch-cache:hit', `expected prefetch cache hit, got ${JSON.stringify(hit)}`);
351
+ writeFileSync(filePath, 'prefetch source changed and longer\n');
352
+ const stale = tryPrefetchCached(filePath);
353
+ if (stale !== null) fail('prefetch-cache:stat-invalidate', `expected stale miss, got ${JSON.stringify(stale)}`);
354
+ drainPrefetchDiskWrites();
355
+ invalidatePrefetchCache(filePath, prefetchWork);
356
+ } finally {
357
+ rmSync(prefetchWork, { recursive: true, force: true });
358
+ }
359
+
360
+ // ── offload helpers ────────────────────────────────────────────────────────
361
+ // countLines must preserve the old split('\n') semantics without allocating
362
+ // an array for very large tool outputs.
363
+ if (offloadInternals.countLines('') !== 0) fail('offload:empty-lines', `expected 0, got ${offloadInternals.countLines('')}`);
364
+ if (offloadInternals.countLines('one') !== 1) fail('offload:single-line', `expected 1, got ${offloadInternals.countLines('one')}`);
365
+ if (offloadInternals.countLines('one\ntwo\n') !== 3) fail('offload:trailing-newline', `expected 3, got ${offloadInternals.countLines('one\ntwo\n')}`);
366
+ const compactedOffload = compactOffloadedToolResultText(`${TOOL_RESULT_OFFLOAD_PREFIX} grep -> /tmp/r1.txt (1 KB, 2 lines)]\nline-1\nline-2`);
367
+ if (!compactedOffload.includes('[preview omitted')) fail('offload:compact-stub', `expected compact preview marker, got ${JSON.stringify(compactedOffload)}`);
368
+
369
+ // Note: cache key + rg argv builders are internal helpers with implicit
370
+ // option-bag schemas that drift faster than this smoke can keep up with.
371
+ // They are exercised end-to-end by the live grep / glob / list tools, so
372
+ // invariant drift would surface there rather than as a silent miss.
373
+
374
+ const totalChecks = coerceCases.length + pathCases.length + 2 /* output paths */
375
+ + 1 /* posix-to-win */
376
+ + 4 /* builtin tool name recovery */
377
+ + 3 /* read-cache key shape */
378
+ + 8 /* grep context byte-exactness */
379
+ + 2 /* scoped-cache root invalidation */
380
+ + 2 /* prefetch cache invalidation */
381
+ + 4 /* offload helpers */;
382
+
383
+ if (failures > 0) {
384
+ console.error(`\n${failures} smoke check(s) failed.`);
385
+ process.exit(1);
386
+ }
387
+ console.log(`builtin-utils-smoke: ${totalChecks} checks passed.`);
388
+ // Explicit success exit: imported builtin/read-dedup/offload modules keep a
389
+ // background flush timer alive, so without this the process never exits and
390
+ // the ci:core `&&` chain stalls after this step. Mirrors the failure path
391
+ // and the sibling smokes (guard-smoke / edit-normalize) which exit explicitly.
392
+ process.exit(0);
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, readdirSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+
5
+ const ROOT = process.cwd();
6
+ const SKIP_DIRS = new Set([
7
+ '.git',
8
+ '.mixdog',
9
+ 'build',
10
+ 'coverage',
11
+ 'dist',
12
+ 'node_modules',
13
+ 'refs',
14
+ 'target',
15
+ ]);
16
+
17
+ function walk(dir, out = []) {
18
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
19
+ if (entry.isDirectory()) {
20
+ if (SKIP_DIRS.has(entry.name)) continue;
21
+ walk(join(dir, entry.name), out);
22
+ continue;
23
+ }
24
+ if (entry.isFile() && entry.name.endsWith('.json')) out.push(join(dir, entry.name));
25
+ }
26
+ return out;
27
+ }
28
+
29
+ const files = walk(ROOT).sort();
30
+ let failed = 0;
31
+ for (const file of files) {
32
+ try {
33
+ JSON.parse(readFileSync(file, 'utf8'));
34
+ } catch (e) {
35
+ failed++;
36
+ process.stderr.write(`FAIL ${relative(ROOT, file)}: ${e.message}\n`);
37
+ }
38
+ }
39
+
40
+ if (failed > 0) {
41
+ process.stderr.write(`JSON check failed: ${failed}/${files.length}\n`);
42
+ process.exit(1);
43
+ }
44
+
45
+ process.stdout.write(`JSON check passed: ${files.length} files\n`);
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'child_process';
3
+ import { existsSync, statSync } from 'fs';
4
+ import { join, relative, resolve } from 'path';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const ROOT = process.cwd();
8
+ const EXTENSIONS = new Set(['.mjs', '.cjs']);
9
+
10
+ function extname(file) {
11
+ const idx = file.lastIndexOf('.');
12
+ return idx >= 0 ? file.slice(idx) : '';
13
+ }
14
+
15
+ function runGit(args) {
16
+ const res = spawnSync('git', args, {
17
+ cwd: ROOT,
18
+ encoding: 'utf8',
19
+ stdio: ['ignore', 'pipe', 'pipe'],
20
+ });
21
+ return res;
22
+ }
23
+
24
+ function isGitRepo() {
25
+ const res = runGit(['rev-parse', '--is-inside-work-tree']);
26
+ return res.status === 0 && res.stdout.trim() === 'true';
27
+ }
28
+
29
+ function collectChanged() {
30
+ const out = new Set();
31
+ const diff = runGit(['diff', '--name-only', 'HEAD']);
32
+ if (diff.status === 0) {
33
+ for (const line of diff.stdout.split(/\r?\n/)) {
34
+ const p = line.trim();
35
+ if (p) out.add(p);
36
+ }
37
+ }
38
+ const untracked = runGit(['ls-files', '--others', '--exclude-standard']);
39
+ if (untracked.status === 0) {
40
+ for (const line of untracked.stdout.split(/\r?\n/)) {
41
+ const p = line.trim();
42
+ if (p) out.add(p);
43
+ }
44
+ }
45
+ return [...out];
46
+ }
47
+
48
+ function fallbackFull() {
49
+ const here = fileURLToPath(import.meta.url);
50
+ const full = resolve(here, '..', 'check-syntax.mjs');
51
+ const res = spawnSync(process.execPath, [full], {
52
+ cwd: ROOT,
53
+ stdio: 'inherit',
54
+ });
55
+ process.exit(res.status ?? 1);
56
+ }
57
+
58
+ if (!isGitRepo()) {
59
+ process.stdout.write('check:changed: not a git repo, running full check:syntax\n');
60
+ fallbackFull();
61
+ }
62
+
63
+ const candidates = collectChanged()
64
+ .filter((p) => EXTENSIONS.has(extname(p)))
65
+ .map((p) => join(ROOT, p))
66
+ .filter((abs) => {
67
+ if (!existsSync(abs)) return false;
68
+ try {
69
+ return statSync(abs).isFile();
70
+ } catch {
71
+ return false;
72
+ }
73
+ });
74
+
75
+ const files = [...new Set(candidates)].sort();
76
+
77
+ if (files.length === 0) {
78
+ process.stdout.write('check:changed: no changed .mjs/.cjs files, running full check:syntax\n');
79
+ fallbackFull();
80
+ }
81
+
82
+ let failed = 0;
83
+ for (const file of files) {
84
+ const rel = relative(ROOT, file);
85
+ const res = spawnSync('node', ['--check', file], {
86
+ encoding: 'utf8',
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ });
89
+ if (res.status !== 0) {
90
+ failed++;
91
+ process.stderr.write(`FAIL ${rel}\n`);
92
+ if (res.stderr) process.stderr.write(res.stderr);
93
+ if (res.stdout) process.stderr.write(res.stdout);
94
+ }
95
+ }
96
+
97
+ if (failed > 0) {
98
+ process.stderr.write(`JavaScript syntax check (changed) failed: ${failed}/${files.length}\n`);
99
+ process.exit(1);
100
+ }
101
+
102
+ process.stdout.write(`JavaScript syntax check (changed) passed: ${files.length} files\n`);
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ import { readdirSync } from 'fs';
3
+ import { join, relative } from 'path';
4
+ import { spawnSync } from 'child_process';
5
+
6
+ const ROOT = process.cwd();
7
+ const SKIP_DIRS = new Set([
8
+ '.git',
9
+ '.mixdog',
10
+ 'build',
11
+ 'coverage',
12
+ 'dist',
13
+ 'node_modules',
14
+ 'refs',
15
+ 'target',
16
+ ]);
17
+ const EXTENSIONS = new Set(['.js', '.cjs', '.mjs']);
18
+
19
+ function extname(file) {
20
+ const idx = file.lastIndexOf('.');
21
+ return idx >= 0 ? file.slice(idx) : '';
22
+ }
23
+
24
+ function walk(dir, out = []) {
25
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
26
+ if (entry.isDirectory()) {
27
+ if (SKIP_DIRS.has(entry.name)) continue;
28
+ walk(join(dir, entry.name), out);
29
+ continue;
30
+ }
31
+ if (!entry.isFile()) continue;
32
+ if (EXTENSIONS.has(extname(entry.name))) out.push(join(dir, entry.name));
33
+ }
34
+ return out;
35
+ }
36
+
37
+ const files = walk(ROOT).sort();
38
+ let failed = 0;
39
+ for (const file of files) {
40
+ const rel = relative(ROOT, file);
41
+ const res = spawnSync('node', ['--check', file], {
42
+ encoding: 'utf8',
43
+ stdio: ['ignore', 'pipe', 'pipe'],
44
+ });
45
+ if (res.status !== 0) {
46
+ failed++;
47
+ process.stderr.write(`FAIL ${rel}\n`);
48
+ if (res.stderr) process.stderr.write(res.stderr);
49
+ if (res.stdout) process.stderr.write(res.stdout);
50
+ }
51
+ }
52
+
53
+ if (failed > 0) {
54
+ process.stderr.write(`JavaScript syntax check failed: ${failed}/${files.length}\n`);
55
+ process.exit(1);
56
+ }
57
+
58
+ process.stdout.write(`JavaScript syntax check passed: ${files.length} files\n`);
@@ -0,0 +1,33 @@
1
+ // Regression: code_graph runs batchable symbol modes (find_symbol/callers/
2
+ // callees/references) once per name when several are requested, so N lookups
3
+ // cost ONE call. A single name (or single-element symbols[]) stays byte-identical
4
+ // to the legacy single-symbol output (no per-symbol header).
5
+ // Uses this repo's own exported symbols. Run: node scripts/code-graph-batch.test.mjs
6
+ import { executeCodeGraphTool } from '../src/agent/orchestrator/tools/code-graph.mjs';
7
+
8
+ const CWD = process.cwd();
9
+ const call = (args) => executeCodeGraphTool('code_graph', args, CWD, null, {});
10
+ let pass = 0, fail = 0;
11
+ const ok = (label, cond, detail = '') => {
12
+ console.log(`${cond ? 'PASS' : 'FAIL'} ${label}${detail ? ' -> ' + detail : ''}`);
13
+ cond ? pass++ : fail++;
14
+ };
15
+
16
+ // 1. Batch via symbols[] — one call, one section per symbol.
17
+ const r1 = await call({ mode: 'find_symbol', symbols: ['peekShellJob', 'killShellJob'] });
18
+ ok('batch find_symbol via symbols[]', /# find_symbol peekShellJob/.test(r1) && /# find_symbol killShellJob/.test(r1));
19
+
20
+ // 2. Batch via space-separated symbol field (most portable across MCP schema caches).
21
+ const r2 = await call({ mode: 'callers', symbol: 'peekShellJob killShellJob' });
22
+ ok('batch callers via space-separated symbol', /# callers peekShellJob/.test(r2) && /# callers killShellJob/.test(r2));
23
+
24
+ // 3. Single symbol — unchanged, NO batch header.
25
+ const r3 = await call({ mode: 'find_symbol', symbol: 'peekShellJob' });
26
+ ok('single find_symbol unchanged (no header)', !/# find_symbol /.test(r3) && r3.length > 0);
27
+
28
+ // 4. Single-element symbols[] resolves (no "provide symbol" error, no header).
29
+ const r4 = await call({ mode: 'find_symbol', symbols: ['peekShellJob'] });
30
+ ok('single-element symbols[] resolves', !/# find_symbol /.test(r4) && r4.length > 0 && !/provide .symbol./.test(r4));
31
+
32
+ console.log(`\n${fail === 0 ? 'ALL OK' : 'FAILURES'}: ${pass} passed, ${fail} failed`);
33
+ process.exit(fail === 0 ? 0 : 1);