@cohortapp/agent-sdk 2.17.0 → 2.18.4

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 (526) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -1,505 +0,0 @@
1
- /**
2
- * Maestro — RAG indexer + search tests (audit H10).
3
- *
4
- * Runs fully offline against a temp better-sqlite3 DB. Embeddings are
5
- * injected via the additive `_embedTexts` (indexer) / `_embedQuery` (search)
6
- * seams so no network call is ever made.
7
- *
8
- * Coverage:
9
- * - FTS search ordering + result shape
10
- * - L18: AND-first multi-term ranking (both-term doc beats one-term doc),
11
- * with OR fallback when the strict conjunction has zero hits
12
- * - L19: relevance is a faithful [0,1] from the NEGATIVE bm25() score,
13
- * correctly ordered, and a zero-score row does not blow up
14
- * - M12: a model/dim-mismatched embedding is NOT returned by semantic search
15
- * - M12: re-index under a new model purges stale-dim embeddings
16
- * - M13: an interrupted embed is completed on the next NON-force run
17
- * - H11: the scoring SELECT does not pull body for all rows (bodies are
18
- * materialized only for the returned topN), and a large candidate
19
- * set is capped by the documented ceiling.
20
- */
21
-
22
- import { test } from "node:test";
23
- import assert from "node:assert/strict";
24
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
25
- import { tmpdir } from "node:os";
26
- import { join } from "node:path";
27
-
28
- import { buildIndex } from "./indexer.mjs";
29
- import { search, DEFAULT_MAX_SEMANTIC_CANDIDATES, ftsRelevance } from "./search.mjs";
30
- import { serializeVector, l2Normalize } from "./embed.mjs";
31
- import DatabaseCtor from "better-sqlite3";
32
-
33
- // ---------------------------------------------------------------------------
34
- // Helpers
35
- // ---------------------------------------------------------------------------
36
-
37
- function makeAgentRoot(files) {
38
- const root = mkdtempSync(join(tmpdir(), "maestro-rag-"));
39
- for (const [rel, content] of Object.entries(files)) {
40
- const abs = join(root, rel);
41
- mkdirSync(join(abs, ".."), { recursive: true });
42
- writeFileSync(abs, content, "utf-8");
43
- }
44
- return root;
45
- }
46
-
47
- function dbPathFor(root) {
48
- return join(root, "state/rag/index/agent.db");
49
- }
50
-
51
- /**
52
- * Deterministic fake embedder. Maps each text to a fixed-dim unit vector
53
- * whose direction is keyed by which "concept" tokens it contains, so that
54
- * texts sharing a concept are similar and the query lands nearest the
55
- * intended doc. Returns the { vectors, model } shape of embed.mjs.
56
- */
57
- function fakeEmbedder(model, dim) {
58
- // Concept axes: each concept gets one hot dimension.
59
- const CONCEPTS = ["alpha", "beta", "gamma"];
60
- return async function _embedTexts(texts) {
61
- const vectors = texts.map((t) => {
62
- const v = new Float32Array(dim);
63
- const lower = String(t || "").toLowerCase();
64
- let any = false;
65
- for (let i = 0; i < CONCEPTS.length && i < dim; i++) {
66
- if (lower.includes(CONCEPTS[i])) { v[i] = 1; any = true; }
67
- }
68
- if (!any) v[dim - 1] = 1; // park empty/probe text on a junk axis
69
- return l2Normalize(v);
70
- });
71
- return { vectors, model, usage: { total_tokens: 0 } };
72
- };
73
- }
74
-
75
- function fakeQuery(model, dim) {
76
- const embedTexts = fakeEmbedder(model, dim);
77
- return async function _embedQuery(q) {
78
- const { vectors } = await embedTexts([q]);
79
- return { vector: vectors[0], model };
80
- };
81
- }
82
-
83
- const SAMPLE = {
84
- "memory/alpha.md": "# Alpha decision\n\nWe decided alpha is the strategy for Q3. alpha alpha.",
85
- "memory/beta.md": "# Beta plan\n\nThe beta rollout covers beta markets. beta beta.",
86
- "knowledge/gamma.md": "# Gamma notes\n\nGamma research on gamma topics. gamma gamma.",
87
- };
88
-
89
- // ---------------------------------------------------------------------------
90
- // FTS path — ordering + result shape
91
- // ---------------------------------------------------------------------------
92
-
93
- test("FTS search returns the matching doc with the documented result shape", async () => {
94
- const root = makeAgentRoot(SAMPLE);
95
- try {
96
- const r = await buildIndex({ agentRoot: root, embeddings: false });
97
- assert.equal(r.added, 3, "all three fixtures indexed");
98
- assert.ok(r.chunks >= 3, "at least one chunk per fixture");
99
-
100
- const hits = await search({ query: "alpha", agentRoot: root, mode: "fts", topN: 5 });
101
- assert.ok(hits.length >= 1, "alpha query returns at least one hit");
102
- assert.equal(hits[0].source_path, "memory/alpha.md", "best hit is the alpha doc");
103
-
104
- const h = hits[0];
105
- for (const k of ["source_path", "kind", "title", "heading", "snippet", "score", "relevance", "retrieval"]) {
106
- assert.ok(k in h, `result has '${k}'`);
107
- }
108
- assert.equal(h.retrieval, "fts");
109
- assert.equal(typeof h.relevance, "number");
110
- assert.ok(h.relevance > 0 && h.relevance <= 1, "relevance in (0,1]");
111
- assert.equal(typeof h.snippet, "string");
112
- } finally {
113
- rmSync(root, { recursive: true, force: true });
114
- }
115
- });
116
-
117
- test("FTS results are ordered by relevance (top hit first)", async () => {
118
- const root = makeAgentRoot(SAMPLE);
119
- try {
120
- await buildIndex({ agentRoot: root, embeddings: false });
121
- const hits = await search({ query: "gamma", agentRoot: root, mode: "fts", topN: 5 });
122
- assert.ok(hits.length >= 1);
123
- assert.equal(hits[0].source_path, "knowledge/gamma.md");
124
- for (let i = 1; i < hits.length; i++) {
125
- assert.ok(hits[i].relevance <= hits[i - 1].relevance + 1e-9, "non-increasing relevance");
126
- }
127
- } finally {
128
- rmSync(root, { recursive: true, force: true });
129
- }
130
- });
131
-
132
- // ---------------------------------------------------------------------------
133
- // L18 — AND-first multi-term ranking (precision) with OR fallback
134
- // ---------------------------------------------------------------------------
135
-
136
- // Corpus where the AND/OR distinction matters: one doc has BOTH query terms,
137
- // one has only the first, one has only the second.
138
- const TWO_TERM = {
139
- "memory/both.md": "# Both\n\nThe orchard manifesto covers orchard and manifesto together. orchard manifesto.",
140
- "memory/orchard.md": "# Orchard only\n\nThis note is about the orchard. orchard orchard orchard orchard.",
141
- "memory/manifesto.md": "# Manifesto only\n\nThis note is about the manifesto. manifesto manifesto manifesto.",
142
- };
143
-
144
- test("L18: a two-term query ranks the doc containing BOTH terms first (AND-first)", async () => {
145
- const root = makeAgentRoot(TWO_TERM);
146
- try {
147
- await buildIndex({ agentRoot: root, embeddings: false });
148
- const hits = await search({ query: "orchard manifesto", agentRoot: root, mode: "fts", topN: 5 });
149
- assert.ok(hits.length >= 1, "two-term query returns hits");
150
- // AND-first means only the both-terms doc satisfies the strict conjunction.
151
- assert.equal(hits[0].source_path, "memory/both.md", "doc with BOTH terms ranks first");
152
- // The one-term-only docs must rank no higher than the both-terms doc.
153
- const both = hits.find((h) => h.source_path === "memory/both.md");
154
- for (const h of hits) {
155
- if (h.source_path === "memory/both.md") continue;
156
- assert.ok(
157
- both.relevance >= h.relevance,
158
- `both-terms doc (${both.relevance}) >= ${h.source_path} (${h.relevance})`,
159
- );
160
- }
161
- } finally {
162
- rmSync(root, { recursive: true, force: true });
163
- }
164
- });
165
-
166
- test("L18: AND-first falls back to OR when no doc contains every term", async () => {
167
- const root = makeAgentRoot(TWO_TERM);
168
- try {
169
- await buildIndex({ agentRoot: root, embeddings: false });
170
- // "orchard" exists; "zucchini" exists in no doc. Strict AND → zero hits, so
171
- // the OR fallback must still surface the orchard docs rather than nothing.
172
- const hits = await search({ query: "orchard zucchini", agentRoot: root, mode: "fts", topN: 5 });
173
- assert.ok(hits.length >= 1, "OR fallback returns the orchard docs");
174
- assert.ok(
175
- hits.some((h) => h.source_path === "memory/orchard.md" || h.source_path === "memory/both.md"),
176
- "an orchard-bearing doc is recovered by the OR fallback",
177
- );
178
- } finally {
179
- rmSync(root, { recursive: true, force: true });
180
- }
181
- });
182
-
183
- // ---------------------------------------------------------------------------
184
- // L19 — faithful [0,1] relevance from the NEGATIVE bm25() score; zero-safe
185
- // ---------------------------------------------------------------------------
186
-
187
- test("L19: FTS relevance is within [0,1], top hit is 1.0, and ordering matches rank", async () => {
188
- const root = makeAgentRoot(TWO_TERM);
189
- try {
190
- await buildIndex({ agentRoot: root, embeddings: false });
191
- const hits = await search({ query: "orchard manifesto", agentRoot: root, mode: "fts", topN: 5 });
192
- assert.ok(hits.length >= 1);
193
- for (const h of hits) {
194
- assert.equal(typeof h.relevance, "number");
195
- assert.ok(Number.isFinite(h.relevance), `relevance is finite (got ${h.relevance})`);
196
- assert.ok(h.relevance >= 0 && h.relevance <= 1, `relevance in [0,1] (got ${h.relevance})`);
197
- // bm25() is negative (lower = better); score must never be positive here.
198
- assert.ok(h.score <= 0, `bm25 score is non-positive (got ${h.score})`);
199
- }
200
- assert.ok(Math.abs(hits[0].relevance - 1) < 1e-9, "top hit relevance is 1.0");
201
- for (let i = 1; i < hits.length; i++) {
202
- assert.ok(
203
- hits[i].relevance <= hits[i - 1].relevance + 1e-9,
204
- "relevance is non-increasing with rank",
205
- );
206
- }
207
- } finally {
208
- rmSync(root, { recursive: true, force: true });
209
- }
210
- });
211
-
212
- test("L19: ftsRelevance transform is zero-safe and never Inf/NaN/out-of-range", () => {
213
- // Direct unit test of the exported transform — the case the old code got
214
- // wrong. bm25() is negative; 0 means "no overlap".
215
- // - top hit (score === best, both negative) → exactly 1.0
216
- // - a weaker (less-negative) score decays toward 0 and stays in [0,1]
217
- // - a row score of 0 → 0 (the old `best / 0` produced Infinity here)
218
- // - a best score of 0 (degenerate set) → 0, never a division blow-up
219
- // - non-finite inputs → 0, never NaN
220
- assert.ok(Math.abs(ftsRelevance(-5, -5) - 1) < 1e-9, "top hit (score===best) is 1.0");
221
- const weak = ftsRelevance(-1, -5);
222
- assert.ok(weak > 0 && weak < 1, `weaker score decays into (0,1) (got ${weak})`);
223
- assert.ok(Math.abs(weak - 0.2) < 1e-9, "ratio best/score is faithful (-5/-1 = 0.2)");
224
- assert.equal(ftsRelevance(0, -5), 0, "row score 0 maps to 0 (no Infinity)");
225
- assert.equal(ftsRelevance(-5, 0), 0, "best score 0 maps to 0 (no division blow-up)");
226
- assert.equal(ftsRelevance(0, 0), 0, "all-zero maps to 0");
227
- assert.equal(ftsRelevance(Infinity, -5), 0, "non-finite score → 0");
228
- assert.equal(ftsRelevance(-5, NaN), 0, "non-finite best → 0");
229
- for (const v of [ftsRelevance(-5, -5), weak, ftsRelevance(0, -5), ftsRelevance(-5, 0)]) {
230
- assert.ok(Number.isFinite(v) && v >= 0 && v <= 1, `relevance ${v} is finite & in [0,1]`);
231
- }
232
- });
233
-
234
- test("L19: a degenerate single-doc FTS result yields a finite, in-range relevance", async () => {
235
- // End-to-end sanity: even the simplest corpus must never emit Inf/NaN
236
- // relevance (the failure mode of the old `bestScore / r.score` ratio).
237
- const root = makeAgentRoot({
238
- "memory/solo.md": "# Solo\n\nwidget widget widget widget widget widget.",
239
- });
240
- try {
241
- await buildIndex({ agentRoot: root, embeddings: false });
242
- const hits = await search({ query: "widget", agentRoot: root, mode: "fts", topN: 5 });
243
- assert.ok(hits.length >= 1, "single-doc corpus returns the doc");
244
- for (const h of hits) {
245
- assert.ok(Number.isFinite(h.relevance), "relevance finite on single-row result");
246
- assert.ok(h.relevance >= 0 && h.relevance <= 1, "relevance in [0,1] on single-row result");
247
- }
248
- // The sole hit is the best hit → relevance pinned to 1.0 (best/best),
249
- // never Infinity (which the old `bestScore / r.score` guard could emit).
250
- assert.ok(Math.abs(hits[0].relevance - 1) < 1e-9, "sole/best hit relevance is exactly 1.0");
251
- } finally {
252
- rmSync(root, { recursive: true, force: true });
253
- }
254
- });
255
-
256
- // ---------------------------------------------------------------------------
257
- // Semantic path — happy ordering via injected offline embeddings
258
- // ---------------------------------------------------------------------------
259
-
260
- test("semantic search ranks the concept-matching doc first (offline)", async () => {
261
- const DIM = 8;
262
- const MODEL = "fake-small";
263
- const root = makeAgentRoot(SAMPLE);
264
- try {
265
- const r = await buildIndex({
266
- agentRoot: root,
267
- embeddings: true,
268
- embedModel: MODEL,
269
- _embedTexts: fakeEmbedder(MODEL, DIM),
270
- });
271
- assert.ok(r.embeds >= 3, "embeddings generated for all chunks");
272
-
273
- const hits = await search({
274
- query: "tell me about beta",
275
- agentRoot: root,
276
- mode: "semantic",
277
- topN: 5,
278
- embedModel: MODEL,
279
- _embedQuery: fakeQuery(MODEL, DIM),
280
- });
281
- assert.ok(hits.length >= 1, "semantic returns hits");
282
- assert.equal(hits[0].source_path, "memory/beta.md", "beta doc ranks first");
283
- assert.equal(hits[0].retrieval, "semantic");
284
- assert.ok(typeof hits[0].snippet === "string" && hits[0].snippet.length > 0, "snippet materialized for topN");
285
- } finally {
286
- rmSync(root, { recursive: true, force: true });
287
- }
288
- });
289
-
290
- // ---------------------------------------------------------------------------
291
- // M12 — model/dim mismatch must NOT be returned
292
- // ---------------------------------------------------------------------------
293
-
294
- test("M12: a mismatched-dim embedding is excluded from semantic results", async () => {
295
- const DIM = 8;
296
- const MODEL = "fake-small";
297
- const root = makeAgentRoot(SAMPLE);
298
- try {
299
- await buildIndex({
300
- agentRoot: root,
301
- embeddings: true,
302
- embedModel: MODEL,
303
- _embedTexts: fakeEmbedder(MODEL, DIM),
304
- });
305
-
306
- // Corrupt one chunk's embedding to a different (model, dim): point the
307
- // gamma chunk at a vector that, if naively scored, would be a strong
308
- // match for a "gamma" query — but it has the wrong dim, so cosine would
309
- // be 0 (length mismatch) and the M12 SQL filter excludes it outright.
310
- const db = new DatabaseCtor(dbPathFor(root));
311
- try {
312
- const gammaRow = db.prepare(
313
- "SELECT rowid FROM chunks WHERE source_path = 'knowledge/gamma.md' LIMIT 1"
314
- ).get();
315
- assert.ok(gammaRow, "gamma chunk exists");
316
- const wrong = l2Normalize(new Float32Array([0, 0, 1, 0])); // dim 4, not 8
317
- db.prepare(
318
- "UPDATE embeddings SET model = ?, dim = ?, vector = ? WHERE chunk_rowid = ?"
319
- ).run("other-model", 4, serializeVector(wrong), gammaRow.rowid);
320
- } finally {
321
- db.close();
322
- }
323
-
324
- const hits = await search({
325
- query: "gamma",
326
- agentRoot: root,
327
- mode: "semantic",
328
- topN: 5,
329
- embedModel: MODEL,
330
- _embedQuery: fakeQuery(MODEL, DIM),
331
- });
332
- // The gamma doc had its embedding moved to a foreign (model, dim) so it
333
- // must NOT appear in results filtered to the query's (MODEL, DIM).
334
- assert.ok(
335
- !hits.some((h) => h.source_path === "knowledge/gamma.md"),
336
- "mismatched-dim gamma row is excluded"
337
- );
338
- } finally {
339
- rmSync(root, { recursive: true, force: true });
340
- }
341
- });
342
-
343
- test("M12: re-index under a new model purges stale-dim embeddings", async () => {
344
- const root = makeAgentRoot(SAMPLE);
345
- try {
346
- await buildIndex({
347
- agentRoot: root,
348
- embeddings: true,
349
- embedModel: "fake-small",
350
- _embedTexts: fakeEmbedder("fake-small", 8),
351
- });
352
-
353
- // Re-index forcing a fresh embed under a NEW model + dim. Force re-chunks
354
- // so newChunks is non-empty, but the purge must also clear old-dim rows.
355
- const r2 = await buildIndex({
356
- agentRoot: root,
357
- force: true,
358
- embeddings: true,
359
- embedModel: "fake-large",
360
- _embedTexts: fakeEmbedder("fake-large", 16),
361
- });
362
- assert.ok(r2.purged >= 0, "purged field present");
363
-
364
- const db = new DatabaseCtor(dbPathFor(root), { readonly: true });
365
- try {
366
- const models = db.prepare("SELECT DISTINCT model, dim FROM embeddings").all();
367
- assert.equal(models.length, 1, "only one (model, dim) survives");
368
- assert.equal(models[0].model, "fake-large");
369
- assert.equal(models[0].dim, 16);
370
- } finally {
371
- db.close();
372
- }
373
- } finally {
374
- rmSync(root, { recursive: true, force: true });
375
- }
376
- });
377
-
378
- // ---------------------------------------------------------------------------
379
- // M13 — interrupted embed completed on next non-force run
380
- // ---------------------------------------------------------------------------
381
-
382
- test("M13: missing embeddings are backfilled on the next non-force run", async () => {
383
- const DIM = 8;
384
- const MODEL = "fake-small";
385
- const root = makeAgentRoot(SAMPLE);
386
- try {
387
- const r1 = await buildIndex({
388
- agentRoot: root,
389
- embeddings: true,
390
- embedModel: MODEL,
391
- _embedTexts: fakeEmbedder(MODEL, DIM),
392
- });
393
- const chunkCount = r1.chunks;
394
- assert.ok(chunkCount >= 3);
395
-
396
- // Simulate a crash mid-embed: delete a subset of embeddings while leaving
397
- // files.mtime untouched (the file is "indexed" but partly unembedded).
398
- const db = new DatabaseCtor(dbPathFor(root));
399
- let deleted;
400
- try {
401
- deleted = db.prepare(
402
- "DELETE FROM embeddings WHERE chunk_rowid IN " +
403
- "(SELECT rowid FROM chunks WHERE source_path = 'memory/beta.md')"
404
- ).run().changes;
405
- assert.ok(deleted >= 1, "deleted at least one embedding to simulate interruption");
406
- const before = db.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n;
407
- assert.equal(before, chunkCount - deleted);
408
- } finally {
409
- db.close();
410
- }
411
-
412
- // Re-run WITHOUT force. The unchanged-file skip would normally skip beta,
413
- // but the missing-embedding sweep (M13) must re-embed the stranded chunks.
414
- const r2 = await buildIndex({
415
- agentRoot: root,
416
- embeddings: true,
417
- embedModel: MODEL,
418
- _embedTexts: fakeEmbedder(MODEL, DIM),
419
- });
420
- assert.equal(r2.added, 0, "no new files");
421
- assert.equal(r2.updated, 0, "no changed files (mtime unchanged)");
422
- assert.equal(r2.embeds, deleted, "exactly the missing embeddings were re-embedded");
423
-
424
- const db2 = new DatabaseCtor(dbPathFor(root), { readonly: true });
425
- try {
426
- const after = db2.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n;
427
- assert.equal(after, chunkCount, "every chunk has an embedding again");
428
- } finally {
429
- db2.close();
430
- }
431
-
432
- // And the beta doc is searchable again semantically.
433
- const hits = await search({
434
- query: "beta", agentRoot: root, mode: "semantic", topN: 5,
435
- embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
436
- });
437
- assert.ok(hits.some((h) => h.source_path === "memory/beta.md"), "beta re-embedded + retrievable");
438
- } finally {
439
- rmSync(root, { recursive: true, force: true });
440
- }
441
- });
442
-
443
- // ---------------------------------------------------------------------------
444
- // H11 — body not pulled for all rows; large candidate set capped
445
- // ---------------------------------------------------------------------------
446
-
447
- test("H11: bodies are materialized only for the returned topN", async () => {
448
- const DIM = 8;
449
- const MODEL = "fake-small";
450
- const root = makeAgentRoot(SAMPLE);
451
- try {
452
- await buildIndex({
453
- agentRoot: root,
454
- embeddings: true,
455
- embedModel: MODEL,
456
- _embedTexts: fakeEmbedder(MODEL, DIM),
457
- });
458
-
459
- const topN = 1;
460
- const hits = await search({
461
- query: "alpha", agentRoot: root, mode: "semantic", topN,
462
- embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
463
- });
464
- assert.equal(hits.length, topN, "exactly topN results");
465
- // The single returned hit carries a non-empty snippet (its body WAS
466
- // fetched), proving snippets are built from the per-topN body fetch and
467
- // not from a body column on the scoring scan.
468
- assert.ok(hits[0].snippet.length > 0, "returned hit has a snippet from its body");
469
- } finally {
470
- rmSync(root, { recursive: true, force: true });
471
- }
472
- });
473
-
474
- test("H11: candidate set above the ceiling is capped, not OOM", async () => {
475
- const DIM = 8;
476
- const MODEL = "fake-small";
477
- const root = makeAgentRoot(SAMPLE);
478
- const prev = process.env.RAG_MAX_SEMANTIC_CANDIDATES;
479
- try {
480
- await buildIndex({
481
- agentRoot: root,
482
- embeddings: true,
483
- embedModel: MODEL,
484
- _embedTexts: fakeEmbedder(MODEL, DIM),
485
- });
486
-
487
- // Force the ceiling below our (tiny) corpus so the cap path is exercised.
488
- process.env.RAG_MAX_SEMANTIC_CANDIDATES = "1";
489
- const hits = await search({
490
- query: "alpha", agentRoot: root, mode: "semantic", topN: 5,
491
- embedModel: MODEL, _embedQuery: fakeQuery(MODEL, DIM),
492
- });
493
- // Cap == 1, so at most one candidate is scanned → at most one result.
494
- assert.ok(hits.length <= 1, "scan capped to the ceiling");
495
- } finally {
496
- if (prev === undefined) delete process.env.RAG_MAX_SEMANTIC_CANDIDATES;
497
- else process.env.RAG_MAX_SEMANTIC_CANDIDATES = prev;
498
- rmSync(root, { recursive: true, force: true });
499
- }
500
- });
501
-
502
- test("DEFAULT_MAX_SEMANTIC_CANDIDATES is exported and sane", () => {
503
- assert.equal(typeof DEFAULT_MAX_SEMANTIC_CANDIDATES, "number");
504
- assert.ok(DEFAULT_MAX_SEMANTIC_CANDIDATES > 0);
505
- });