@cohortapp/agent-sdk 2.3.0

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 (731) hide show
  1. package/.claude/commands/init-agent.md +104 -0
  2. package/.claude/commands/init-maestro.md +1187 -0
  3. package/.claude/settings.json +161 -0
  4. package/.env.example +216 -0
  5. package/README.md +632 -0
  6. package/agents/browser-operator/agent.md +52 -0
  7. package/agents/calendar-ops/agent.md +50 -0
  8. package/agents/communications/agent.md +96 -0
  9. package/agents/decision-log/agent.md +65 -0
  10. package/agents/desktop-operator/agent.md +59 -0
  11. package/agents/gmail-operator/agent.md +62 -0
  12. package/agents/inbound-dispatcher/agent.md +66 -0
  13. package/agents/inbox-processor/agent.md +39 -0
  14. package/agents/pmo-execution/agent.md +60 -0
  15. package/agents/session-spawner/agent.md +64 -0
  16. package/agents/slack-operator/agent.md +60 -0
  17. package/agents/whatsapp-operator/agent.md +60 -0
  18. package/agents/workflow-automation/agent.md +61 -0
  19. package/archetypes/altitudes/c-suite.yaml +58 -0
  20. package/archetypes/altitudes/founder.yaml +68 -0
  21. package/archetypes/altitudes/senior-manager.yaml +63 -0
  22. package/archetypes/altitudes/svp.yaml +60 -0
  23. package/archetypes/altitudes/vp.yaml +50 -0
  24. package/archetypes/archetype.schema.json +77 -0
  25. package/archetypes/base.yaml +47 -0
  26. package/archetypes/capabilities/commercial-leader.yaml +159 -0
  27. package/archetypes/capabilities/compliance-officer.yaml +159 -0
  28. package/archetypes/capabilities/executive-operator.yaml +169 -0
  29. package/archetypes/capabilities/finance-leader.yaml +162 -0
  30. package/archetypes/capabilities/operations-leader.yaml +154 -0
  31. package/archetypes/capabilities/product-leader.yaml +148 -0
  32. package/archetypes/capabilities/technical-leader.yaml +146 -0
  33. package/archetypes/functions/commercial-leader.yaml +62 -0
  34. package/archetypes/functions/compliance-officer.yaml +64 -0
  35. package/archetypes/functions/executive-operator.yaml +70 -0
  36. package/archetypes/functions/finance-leader.yaml +70 -0
  37. package/archetypes/functions/operations-leader.yaml +62 -0
  38. package/archetypes/functions/product-leader.yaml +61 -0
  39. package/archetypes/functions/technical-leader.yaml +57 -0
  40. package/bin/cohort-mcp.mjs +81 -0
  41. package/bin/maestro.mjs +3516 -0
  42. package/bin/maestro.test.mjs +1015 -0
  43. package/desktop-control/README.md +56 -0
  44. package/desktop-control/app-profiles/gmail.yaml +120 -0
  45. package/desktop-control/app-profiles/slack.yaml +315 -0
  46. package/desktop-control/app-profiles/whatsapp.yaml +107 -0
  47. package/docs/architecture/agent-topology.md +2239 -0
  48. package/docs/architecture/archetype-agent-factory.md +110 -0
  49. package/docs/architecture/collective-memory-and-org-mesh.md +115 -0
  50. package/docs/architecture/continuous-monitoring.md +221 -0
  51. package/docs/architecture/mcp-capability-map.md +585 -0
  52. package/docs/architecture/system-architecture.md +1272 -0
  53. package/docs/company-context/README.md +40 -0
  54. package/docs/guides/agent-persona-setup.md +600 -0
  55. package/docs/guides/agents-observe-setup.md +64 -0
  56. package/docs/guides/billing-console-keys.md +88 -0
  57. package/docs/guides/ccxray-diagnostics.md +65 -0
  58. package/docs/guides/channel-bus.md +127 -0
  59. package/docs/guides/claude-mem-setup.md +79 -0
  60. package/docs/guides/claude-pace-setup.md +56 -0
  61. package/docs/guides/claudraband-sessions.md +98 -0
  62. package/docs/guides/clawteam-swarm.md +116 -0
  63. package/docs/guides/code-review-graph-setup.md +86 -0
  64. package/docs/guides/email-setup.md +431 -0
  65. package/docs/guides/mac-mini.md +119 -0
  66. package/docs/guides/media-generation-setup.md +349 -0
  67. package/docs/guides/model-routing.md +162 -0
  68. package/docs/guides/observability-otel.md +265 -0
  69. package/docs/guides/org-onboarding.md +132 -0
  70. package/docs/guides/outbound-governance-setup.md +437 -0
  71. package/docs/guides/pdf-generation-setup.md +315 -0
  72. package/docs/guides/poller-daemon-setup.md +563 -0
  73. package/docs/guides/rag-context-setup.md +459 -0
  74. package/docs/guides/self-optimization-pattern.md +82 -0
  75. package/docs/guides/setup-wizard.md +178 -0
  76. package/docs/guides/slack-setup.md +350 -0
  77. package/docs/guides/telegram-setup.md +227 -0
  78. package/docs/guides/twilio-subaccounts-setup.md +223 -0
  79. package/docs/guides/verification.md +128 -0
  80. package/docs/guides/voice-mode.md +188 -0
  81. package/docs/guides/voice-sms-setup.md +698 -0
  82. package/docs/guides/webhook-relay-setup.md +349 -0
  83. package/docs/guides/whatsapp-setup.md +288 -0
  84. package/docs/prompts/board-pack-cover-template.md +36 -0
  85. package/docs/prompts/decision-recommendation-template.md +88 -0
  86. package/docs/prompts/followup-message-template.md +141 -0
  87. package/docs/prompts/investor-letter-template.md +52 -0
  88. package/docs/prompts/morning-brief-template.md +82 -0
  89. package/docs/prompts/presentation-template.md +58 -0
  90. package/docs/prompts/weekly-strategic-memo-template.md +104 -0
  91. package/docs/research/hallucinated-tool-output-investigation.md +151 -0
  92. package/docs/runbooks/backup-restore.md +205 -0
  93. package/docs/runbooks/cohort-cutover.md +129 -0
  94. package/docs/runbooks/fleet-operations.md +200 -0
  95. package/docs/runbooks/incident-response.md +226 -0
  96. package/docs/runbooks/mac-mini-bootstrap.md +431 -0
  97. package/docs/runbooks/perpetual-operations.md +509 -0
  98. package/docs/runbooks/recovery-and-failover.md +260 -0
  99. package/framework-features.json +267 -0
  100. package/ingest/README.md +87 -0
  101. package/lib/action-executor.js +689 -0
  102. package/lib/action-executor.test.mjs +871 -0
  103. package/lib/agent-root.mjs +37 -0
  104. package/lib/archetype.mjs +236 -0
  105. package/lib/archetype.test.mjs +132 -0
  106. package/lib/autonomy.mjs +114 -0
  107. package/lib/autonomy.test.mjs +66 -0
  108. package/lib/backlog.mjs +358 -0
  109. package/lib/backlog.test.mjs +266 -0
  110. package/lib/budget-guard.mjs +279 -0
  111. package/lib/budget-guard.test.mjs +291 -0
  112. package/lib/cadence-bus-schedule.test.mjs +194 -0
  113. package/lib/cadence-bus.mjs +1120 -0
  114. package/lib/cadence-bus.test.mjs +720 -0
  115. package/lib/cadences.mjs +205 -0
  116. package/lib/cadences.test.mjs +125 -0
  117. package/lib/capability.mjs +154 -0
  118. package/lib/capability.test.mjs +78 -0
  119. package/lib/channels/base-adapter.mjs +719 -0
  120. package/lib/channels/base-adapter.test.mjs +590 -0
  121. package/lib/channels/channel.mjs +128 -0
  122. package/lib/channels/channels.test.mjs +371 -0
  123. package/lib/channels/contract.mjs +215 -0
  124. package/lib/channels/contract.test.mjs +137 -0
  125. package/lib/channels/conversation-resolver.mjs +95 -0
  126. package/lib/channels/gmail/adapter.mjs +87 -0
  127. package/lib/channels/inbox-item.mjs +255 -0
  128. package/lib/channels/inbox-item.test.mjs +335 -0
  129. package/lib/channels/index.mjs +94 -0
  130. package/lib/channels/orgmail/adapter.mjs +353 -0
  131. package/lib/channels/orgmail/adapter.test.mjs +311 -0
  132. package/lib/channels/pairing.mjs +363 -0
  133. package/lib/channels/pairing.test.mjs +270 -0
  134. package/lib/channels/registry.mjs +164 -0
  135. package/lib/channels/slack/adapter.mjs +317 -0
  136. package/lib/channels/slack-adapter.test.mjs +212 -0
  137. package/lib/channels/sms/adapter.mjs +43 -0
  138. package/lib/channels/telegram/adapter.mjs +432 -0
  139. package/lib/channels/telegram-adapter.test.mjs +306 -0
  140. package/lib/channels/voice/adapter.mjs +301 -0
  141. package/lib/channels/voice/adapter.test.mjs +278 -0
  142. package/lib/channels/whatsapp/adapter-baileys.mjs +587 -0
  143. package/lib/channels/whatsapp/adapter-baileys.test.mjs +359 -0
  144. package/lib/channels/whatsapp/adapter-twilio.mjs +65 -0
  145. package/lib/channels/whatsapp/baileys-typing.test.mjs +154 -0
  146. package/lib/charter.mjs +256 -0
  147. package/lib/charter.test.mjs +89 -0
  148. package/lib/claude-bin.mjs +134 -0
  149. package/lib/claude-bin.test.mjs +75 -0
  150. package/lib/collective/capture.mjs +185 -0
  151. package/lib/collective/capture.test.mjs +121 -0
  152. package/lib/collective/cards.mjs +201 -0
  153. package/lib/collective/cards.test.mjs +114 -0
  154. package/lib/collective/config.mjs +186 -0
  155. package/lib/collective/config.test.mjs +123 -0
  156. package/lib/collective/global-config.mjs +113 -0
  157. package/lib/collective/global-config.test.mjs +75 -0
  158. package/lib/collective/presence.mjs +201 -0
  159. package/lib/collective/presence.test.mjs +95 -0
  160. package/lib/collective/recall.mjs +215 -0
  161. package/lib/collective/recall.test.mjs +116 -0
  162. package/lib/comms/send-gate.mjs +554 -0
  163. package/lib/comms/send-gate.test.mjs +577 -0
  164. package/lib/comms.mjs +67 -0
  165. package/lib/comms.test.mjs +41 -0
  166. package/lib/diagnostics/alerts.mjs +424 -0
  167. package/lib/diagnostics/alerts.test.mjs +318 -0
  168. package/lib/diagnostics/backup-freshness.mjs +188 -0
  169. package/lib/diagnostics/backup-freshness.test.mjs +185 -0
  170. package/lib/diagnostics/counters.mjs +269 -0
  171. package/lib/diagnostics/counters.test.mjs +206 -0
  172. package/lib/diagnostics/events.mjs +188 -0
  173. package/lib/diagnostics/events.test.mjs +290 -0
  174. package/lib/diagnostics/otel.mjs +237 -0
  175. package/lib/diagnostics/otel.test.mjs +196 -0
  176. package/lib/diagnostics/trace.mjs +216 -0
  177. package/lib/diagnostics/trace.test.mjs +251 -0
  178. package/lib/env-compat.mjs +74 -0
  179. package/lib/env-compat.test.mjs +104 -0
  180. package/lib/feature-init.mjs +331 -0
  181. package/lib/fs-atomic.mjs +112 -0
  182. package/lib/fs-atomic.test.mjs +72 -0
  183. package/lib/fs-ownership.mjs +111 -0
  184. package/lib/fs-ownership.test.mjs +158 -0
  185. package/lib/hooks/bus.mjs +347 -0
  186. package/lib/hooks/bus.test.mjs +387 -0
  187. package/lib/index.js +16 -0
  188. package/lib/learning/config.mjs +106 -0
  189. package/lib/learning/config.test.mjs +75 -0
  190. package/lib/learning/counters.mjs +156 -0
  191. package/lib/learning/counters.test.mjs +69 -0
  192. package/lib/learning/curator-consolidate.test.mjs +238 -0
  193. package/lib/learning/curator.mjs +453 -0
  194. package/lib/learning/curator.test.mjs +106 -0
  195. package/lib/learning/log.mjs +40 -0
  196. package/lib/learning/reflect.mjs +534 -0
  197. package/lib/learning/reflect.test.mjs +0 -0
  198. package/lib/learning/session-index.mjs +352 -0
  199. package/lib/learning/session-index.test.mjs +125 -0
  200. package/lib/learning/skill-writer.mjs +474 -0
  201. package/lib/learning/skill-writer.test.mjs +210 -0
  202. package/lib/mcp/server.mjs +328 -0
  203. package/lib/mcp/server.test.mjs +400 -0
  204. package/lib/model-router/auth-profiles.mjs +758 -0
  205. package/lib/model-router/auth-profiles.test.mjs +580 -0
  206. package/lib/model-router/catalog/anthropic.yaml +153 -0
  207. package/lib/model-router/catalog/deepseek.yaml +86 -0
  208. package/lib/model-router/catalog/moonshot.yaml +81 -0
  209. package/lib/model-router/catalog/qwen.yaml +114 -0
  210. package/lib/model-router/catalog.mjs +925 -0
  211. package/lib/model-router/catalog.test.mjs +385 -0
  212. package/lib/model-router/economics.mjs +564 -0
  213. package/lib/model-router/economics.test.mjs +344 -0
  214. package/lib/model-router/failover.mjs +298 -0
  215. package/lib/model-router/failover.test.mjs +439 -0
  216. package/lib/model-router/health.mjs +453 -0
  217. package/lib/model-router/health.test.mjs +338 -0
  218. package/lib/model-router/integration-coverage.test.mjs +829 -0
  219. package/lib/model-router/integration.test.mjs +564 -0
  220. package/lib/model-router/ledger.mjs +402 -0
  221. package/lib/model-router/ledger.test.mjs +382 -0
  222. package/lib/model-router/llm-task.mjs +515 -0
  223. package/lib/model-router/llm-task.test.mjs +392 -0
  224. package/lib/model-router/org-credentials.mjs +260 -0
  225. package/lib/model-router/org-credentials.test.mjs +265 -0
  226. package/lib/model-router/pricing-refresh.mjs +463 -0
  227. package/lib/model-router/pricing-refresh.test.mjs +286 -0
  228. package/lib/model-router/reconcile.mjs +429 -0
  229. package/lib/model-router/reconcile.test.mjs +316 -0
  230. package/lib/model-router/repair.mjs +471 -0
  231. package/lib/model-router/repair.test.mjs +180 -0
  232. package/lib/model-router/resolve.mjs +1206 -0
  233. package/lib/model-router/spawn.mjs +497 -0
  234. package/lib/model-router/spawn.test.mjs +425 -0
  235. package/lib/model-router/taxonomy.mjs +893 -0
  236. package/lib/model-router/taxonomy.test.mjs +410 -0
  237. package/lib/model-router.mjs +677 -0
  238. package/lib/model-router.test.mjs +907 -0
  239. package/lib/org/activity.mjs +211 -0
  240. package/lib/org/activity.test.mjs +134 -0
  241. package/lib/org/approvals.mjs +448 -0
  242. package/lib/org/approvals.test.mjs +216 -0
  243. package/lib/org/awareness.mjs +222 -0
  244. package/lib/org/awareness.test.mjs +159 -0
  245. package/lib/org/board.mjs +229 -0
  246. package/lib/org/board.test.mjs +177 -0
  247. package/lib/org/bootstrap-context.mjs +169 -0
  248. package/lib/org/bootstrap-context.test.mjs +153 -0
  249. package/lib/org/client.mjs +1628 -0
  250. package/lib/org/client.test.mjs +1107 -0
  251. package/lib/org/cohort-client.mjs +67 -0
  252. package/lib/org/cohort-client.test.mjs +126 -0
  253. package/lib/org/cost-sync.mjs +227 -0
  254. package/lib/org/cost-sync.test.mjs +153 -0
  255. package/lib/org/doctor.mjs +212 -0
  256. package/lib/org/doctor.test.mjs +212 -0
  257. package/lib/org/handoff.mjs +293 -0
  258. package/lib/org/handoff.test.mjs +269 -0
  259. package/lib/org/integration-tools.mjs +182 -0
  260. package/lib/org/integration-tools.test.mjs +160 -0
  261. package/lib/org/keys.mjs +131 -0
  262. package/lib/org/keys.test.mjs +92 -0
  263. package/lib/org/knowledge.mjs +463 -0
  264. package/lib/org/knowledge.test.mjs +319 -0
  265. package/lib/org/leases.mjs +335 -0
  266. package/lib/org/leases.test.mjs +235 -0
  267. package/lib/org/mesh-integration.test.mjs +127 -0
  268. package/lib/org/mesh.mjs +459 -0
  269. package/lib/org/mesh.test.mjs +345 -0
  270. package/lib/org/messaging.mjs +503 -0
  271. package/lib/org/messaging.test.mjs +238 -0
  272. package/lib/org/policy.mjs +345 -0
  273. package/lib/org/policy.test.mjs +237 -0
  274. package/lib/org/protocol.checksum +1 -0
  275. package/lib/org/protocol.checksum.test.mjs +90 -0
  276. package/lib/org/protocol.mjs +967 -0
  277. package/lib/org/protocol.test.mjs +264 -0
  278. package/lib/org/registry.mjs +194 -0
  279. package/lib/org/registry.test.mjs +100 -0
  280. package/lib/org/tool-surface-integration.test.mjs +120 -0
  281. package/lib/org/tool-surface.mjs +2535 -0
  282. package/lib/org/tool-surface.test.mjs +589 -0
  283. package/lib/org/ui-parity.mjs +3236 -0
  284. package/lib/org/ui-parity.test.mjs +348 -0
  285. package/lib/org/verify.mjs +176 -0
  286. package/lib/org/verify.test.mjs +194 -0
  287. package/lib/rag/embed.mjs +188 -0
  288. package/lib/rag/indexer.mjs +425 -0
  289. package/lib/rag/rag.test.mjs +505 -0
  290. package/lib/rag/search.mjs +475 -0
  291. package/lib/rate-guard.mjs +246 -0
  292. package/lib/rate-guard.test.mjs +201 -0
  293. package/lib/render.mjs +112 -0
  294. package/lib/render.test.mjs +68 -0
  295. package/lib/resource-governor.mjs +297 -0
  296. package/lib/resource-governor.test.mjs +262 -0
  297. package/lib/scheduling/dynamic-jobs.mjs +675 -0
  298. package/lib/scheduling/dynamic-jobs.test.mjs +344 -0
  299. package/lib/scheduling/jitter.mjs +0 -0
  300. package/lib/scheduling/jitter.test.mjs +140 -0
  301. package/lib/secrets/broker.mjs +315 -0
  302. package/lib/secrets/broker.test.mjs +280 -0
  303. package/lib/secrets/providers.mjs +461 -0
  304. package/lib/secrets/providers.test.mjs +274 -0
  305. package/lib/security/audit-engine.mjs +684 -0
  306. package/lib/security/audit-engine.test.mjs +389 -0
  307. package/lib/security/coerce-args.mjs +552 -0
  308. package/lib/security/coerce-args.test.mjs +281 -0
  309. package/lib/security/dangerous-tools.mjs +97 -0
  310. package/lib/security/dangerous-tools.test.mjs +68 -0
  311. package/lib/security/external-content.mjs +145 -0
  312. package/lib/security/external-content.test.mjs +67 -0
  313. package/lib/security/redact.mjs +592 -0
  314. package/lib/security/redact.test.mjs +441 -0
  315. package/lib/security/secret-equal.mjs +73 -0
  316. package/lib/security/secret-equal.test.mjs +55 -0
  317. package/lib/session-permissions.mjs +101 -0
  318. package/lib/session-permissions.test.mjs +100 -0
  319. package/lib/setup/claude-probe.mjs +74 -0
  320. package/lib/setup/completeness.mjs +175 -0
  321. package/lib/setup/completeness.test.mjs +110 -0
  322. package/lib/setup/context-pack.mjs +173 -0
  323. package/lib/setup/context-pack.test.mjs +89 -0
  324. package/lib/setup/enrich.mjs +277 -0
  325. package/lib/setup/enrich.test.mjs +115 -0
  326. package/lib/setup/enroll-from-cohort.mjs +441 -0
  327. package/lib/setup/enroll-from-cohort.test.mjs +233 -0
  328. package/lib/setup/integration.test.mjs +162 -0
  329. package/lib/setup/io.mjs +360 -0
  330. package/lib/setup/io.test.mjs +77 -0
  331. package/lib/setup/run-generator.mjs +81 -0
  332. package/lib/setup/runner.mjs +244 -0
  333. package/lib/setup/runner.test.mjs +132 -0
  334. package/lib/setup/sections/comms.mjs +173 -0
  335. package/lib/setup/sections/company.mjs +120 -0
  336. package/lib/setup/sections/enrich.mjs +138 -0
  337. package/lib/setup/sections/identity.mjs +182 -0
  338. package/lib/setup/sections/identity.test.mjs +140 -0
  339. package/lib/setup/sections/learning.mjs +153 -0
  340. package/lib/setup/sections/learning.test.mjs +81 -0
  341. package/lib/setup/sections/messaging.mjs +219 -0
  342. package/lib/setup/sections/messaging.test.mjs +127 -0
  343. package/lib/setup/sections/model.mjs +102 -0
  344. package/lib/setup/sections/operating-model.mjs +78 -0
  345. package/lib/setup/sections/org.mjs +475 -0
  346. package/lib/setup/sections/org.test.mjs +313 -0
  347. package/lib/setup/sections/orgmail.mjs +173 -0
  348. package/lib/setup/sections/orgmail.test.mjs +118 -0
  349. package/lib/setup/sections/recovery.mjs +159 -0
  350. package/lib/setup/sections/recovery.test.mjs +98 -0
  351. package/lib/setup/sections/tools.mjs +132 -0
  352. package/lib/setup/sections/verify.mjs +97 -0
  353. package/lib/setup/sot.mjs +205 -0
  354. package/lib/setup/sot.test.mjs +81 -0
  355. package/lib/setup/state.mjs +151 -0
  356. package/lib/setup/state.test.mjs +92 -0
  357. package/lib/singleton.js +229 -0
  358. package/lib/singleton.test.mjs +135 -0
  359. package/lib/telemetry/alerts.mjs +216 -0
  360. package/lib/telemetry/alerts.test.mjs +109 -0
  361. package/lib/telemetry/collect.mjs +512 -0
  362. package/lib/telemetry/collect.test.mjs +202 -0
  363. package/lib/tool-definitions-integration.test.mjs +83 -0
  364. package/lib/tool-definitions.js +738 -0
  365. package/lib/tool-definitions.test.mjs +437 -0
  366. package/lib/util/fetch-timeout.mjs +136 -0
  367. package/lib/util/fetch-timeout.test.mjs +202 -0
  368. package/lib/util/reconnect.mjs +343 -0
  369. package/lib/util/reconnect.test.mjs +369 -0
  370. package/lib/util/unhandled.mjs +205 -0
  371. package/lib/util/unhandled.test.mjs +216 -0
  372. package/lib/voice/context-loader.mjs +466 -0
  373. package/lib/voice/index.mjs +100 -0
  374. package/lib/voice/openai-realtime.mjs +510 -0
  375. package/lib/voice/outbound.mjs +542 -0
  376. package/lib/voice/outbound.test.mjs +69 -0
  377. package/lib/voice/post-call-brief.mjs +428 -0
  378. package/lib/voice/provider.mjs +52 -0
  379. package/lib/voice/session-rotation.mjs +257 -0
  380. package/lib/voice/stt.mjs +161 -0
  381. package/lib/voice/stt.test.mjs +226 -0
  382. package/lib/voice/tool-bridge.mjs +370 -0
  383. package/lib/voice/tts.mjs +104 -0
  384. package/lib/voice/twilio-sip-bridge.mjs +288 -0
  385. package/lib/voice/voice.test.mjs +990 -0
  386. package/mcp/README.md +80 -0
  387. package/package.json +151 -0
  388. package/plugins/maestro-skills/plugin.json +139 -0
  389. package/plugins/maestro-skills/skills/agents-observe.md +110 -0
  390. package/plugins/maestro-skills/skills/board-deck.md +68 -0
  391. package/plugins/maestro-skills/skills/books-close.md +77 -0
  392. package/plugins/maestro-skills/skills/brand-steward.md +121 -0
  393. package/plugins/maestro-skills/skills/calendar-plan.md +57 -0
  394. package/plugins/maestro-skills/skills/call-working-sessions.md +124 -0
  395. package/plugins/maestro-skills/skills/ccxray-diagnostics.md +91 -0
  396. package/plugins/maestro-skills/skills/claude-pace.md +61 -0
  397. package/plugins/maestro-skills/skills/code-review-graph.md +99 -0
  398. package/plugins/maestro-skills/skills/crm-pipeline.md +65 -0
  399. package/plugins/maestro-skills/skills/decision-brief.md +89 -0
  400. package/plugins/maestro-skills/skills/directory-hygiene.md +125 -0
  401. package/plugins/maestro-skills/skills/draft-comms.md +84 -0
  402. package/plugins/maestro-skills/skills/evening-wrap.md +53 -0
  403. package/plugins/maestro-skills/skills/files-find.md +65 -0
  404. package/plugins/maestro-skills/skills/generative-ui.md +228 -0
  405. package/plugins/maestro-skills/skills/hiring-triage.md +74 -0
  406. package/plugins/maestro-skills/skills/inbox-triage.md +61 -0
  407. package/plugins/maestro-skills/skills/mail-triage.md +86 -0
  408. package/plugins/maestro-skills/skills/morning-brief.md +54 -0
  409. package/plugins/maestro-skills/skills/native-artifacts.md +157 -0
  410. package/plugins/maestro-skills/skills/org-board.md +133 -0
  411. package/plugins/maestro-skills/skills/org-credential.md +68 -0
  412. package/plugins/maestro-skills/skills/org-recall.md +81 -0
  413. package/plugins/maestro-skills/skills/pipeline-review.md +76 -0
  414. package/plugins/maestro-skills/skills/regulatory-status.md +81 -0
  415. package/plugins/maestro-skills/skills/router-why.md +78 -0
  416. package/plugins/maestro-skills/skills/schedule-meeting.md +91 -0
  417. package/plugins/maestro-skills/skills/session-search.md +71 -0
  418. package/plugins/maestro-skills/skills/set-reminder.md +93 -0
  419. package/plugins/maestro-skills/skills/slack-followup.md +64 -0
  420. package/plugins/maestro-skills/skills/team-activity.md +86 -0
  421. package/plugins/maestro-skills/skills/weekly-memo.md +70 -0
  422. package/policies/action-classification.yaml +114 -0
  423. package/policies/ai-disclosure.yaml +294 -0
  424. package/policies/communication-style.md +139 -0
  425. package/policies/information-barriers.yaml +118 -0
  426. package/policies/prompt-injection-defence.yaml +138 -0
  427. package/public/assets/icon-dark.png +0 -0
  428. package/public/assets/icon-dark.svg +9 -0
  429. package/public/assets/icon-light.svg +9 -0
  430. package/public/assets/logo-dark.svg +15 -0
  431. package/public/assets/logo-light.svg +15 -0
  432. package/scaffold/.mcp.json +7 -0
  433. package/scaffold/CLAUDE.md +368 -0
  434. package/scaffold/config/agent.json +55 -0
  435. package/scaffold/config/agent.ts +76 -0
  436. package/scaffold/config/agent.ts.example +89 -0
  437. package/scaffold/config/alerts.yaml +23 -0
  438. package/scaffold/config/allowlist.yaml.example +25 -0
  439. package/scaffold/config/caller-id-map.yaml +46 -0
  440. package/scaffold/config/collective.yaml +49 -0
  441. package/scaffold/config/company.json +20 -0
  442. package/scaffold/config/known-agents.json +6 -0
  443. package/scaffold/config/learning.yaml +55 -0
  444. package/scaffold/config/model-routing.yaml.example +104 -0
  445. package/scaffold/config/org.yaml +25 -0
  446. package/scaffold/config/orgmail.yaml.example +19 -0
  447. package/scaffold/config/recovery.yaml +72 -0
  448. package/scaffold/config/secrets.yaml +27 -0
  449. package/scaffold/config/slack.yaml.example +35 -0
  450. package/scaffold/config/telegram.yaml.example +38 -0
  451. package/scaffold/config/voice.yaml.example +89 -0
  452. package/scaffold/config/whatsapp.yaml.example +39 -0
  453. package/schedules/README.md +49 -0
  454. package/schedules/triggers/backlog-executor.md +102 -0
  455. package/schedules/triggers/brand-steward.md +72 -0
  456. package/schedules/triggers/daily-evening-wrap.md +159 -0
  457. package/schedules/triggers/daily-midday-sweep.md +58 -0
  458. package/schedules/triggers/daily-morning-brief.md +55 -0
  459. package/schedules/triggers/directory-hygiene.md +81 -0
  460. package/schedules/triggers/dynamic-jobs.md +40 -0
  461. package/schedules/triggers/inbox-processor.md +115 -0
  462. package/schedules/triggers/meeting-action-capture.md +60 -0
  463. package/schedules/triggers/meeting-prep.md +69 -0
  464. package/schedules/triggers/messaging-inbound.md +50 -0
  465. package/schedules/triggers/org-pulse.md +24 -0
  466. package/schedules/triggers/quarterly-self-assessment.md +54 -0
  467. package/schedules/triggers/weekly-engineering-health.md +37 -0
  468. package/schedules/triggers/weekly-execution.md +65 -0
  469. package/schedules/triggers/weekly-hiring.md +53 -0
  470. package/schedules/triggers/weekly-priorities.md +38 -0
  471. package/schedules/triggers/weekly-strategic-memo.md +124 -0
  472. package/scripts/archive-email.sh +55 -0
  473. package/scripts/cadence/cadence-status.mjs +36 -0
  474. package/scripts/cadence/enqueue-cadence-tick.mjs +174 -0
  475. package/scripts/cadence/enqueue-cadence-tick.test.mjs +187 -0
  476. package/scripts/cadence/launchd-cadence-wrapper.sh +85 -0
  477. package/scripts/cadence/launchd-cloud-relay-wrapper.sh +95 -0
  478. package/scripts/cadence/launchd-socket-mode-wrapper.sh +95 -0
  479. package/scripts/ci/check-docs-accuracy.mjs +493 -0
  480. package/scripts/ci/check-docs-accuracy.test.mjs +409 -0
  481. package/scripts/ci/check-exports-exist.mjs +140 -0
  482. package/scripts/ci/check-files-exist.mjs +107 -0
  483. package/scripts/ci/check-no-build-artifacts.mjs +111 -0
  484. package/scripts/ci/check-no-build-artifacts.test.mjs +71 -0
  485. package/scripts/ci/check-no-confidential.mjs +198 -0
  486. package/scripts/ci/check-no-conflict-markers.mjs +169 -0
  487. package/scripts/ci/check-no-residual-identity.mjs +163 -0
  488. package/scripts/ci/check-no-residual-identity.test.mjs +89 -0
  489. package/scripts/ci/check-tarball-fidelity.mjs +205 -0
  490. package/scripts/ci/check-unresolved-tokens.mjs +83 -0
  491. package/scripts/ci/check.mjs +109 -0
  492. package/scripts/ci/check.test.mjs +194 -0
  493. package/scripts/ci/run-coverage.mjs +82 -0
  494. package/scripts/ci/run-tests.mjs +71 -0
  495. package/scripts/cloud-relay/README.md +59 -0
  496. package/scripts/cloud-relay/index.mjs +233 -0
  497. package/scripts/cloud-relay/package.json +15 -0
  498. package/scripts/cloud-relay/railway.json +13 -0
  499. package/scripts/cloud-relay/voice/README.md +94 -0
  500. package/scripts/cloud-relay/voice/package-lock.json +39 -0
  501. package/scripts/cloud-relay/voice/package.json +16 -0
  502. package/scripts/cloud-relay/voice/railway.json +13 -0
  503. package/scripts/cloud-relay/voice/server.mjs +532 -0
  504. package/scripts/collective/hook-runner.mjs +211 -0
  505. package/scripts/collective/hook-runner.test.mjs +90 -0
  506. package/scripts/collective/org-pulse.mjs +72 -0
  507. package/scripts/collective/org-sync.mjs +61 -0
  508. package/scripts/collective/recall.mjs +45 -0
  509. package/scripts/collective/who.mjs +30 -0
  510. package/scripts/comms-monitor.sh +288 -0
  511. package/scripts/configure-whatsapp-sandbox.sh +201 -0
  512. package/scripts/continuous-monitor.sh +91 -0
  513. package/scripts/cost/fleet-digest.mjs +407 -0
  514. package/scripts/cost/fleet-digest.test.mjs +207 -0
  515. package/scripts/cost/track-claude-usage.mjs +169 -0
  516. package/scripts/daemon/agent-daemon.mjs +989 -0
  517. package/scripts/daemon/agent-daemon.test.mjs +525 -0
  518. package/scripts/daemon/cadence-consumer-governance.test.mjs +220 -0
  519. package/scripts/daemon/cadence-consumer.mjs +1080 -0
  520. package/scripts/daemon/cadence-consumer.test.mjs +770 -0
  521. package/scripts/daemon/cadence-handlers.mjs +1121 -0
  522. package/scripts/daemon/cadence-handlers.test.mjs +617 -0
  523. package/scripts/daemon/classifier.mjs +704 -0
  524. package/scripts/daemon/classifier.test.mjs +238 -0
  525. package/scripts/daemon/classify-kind.mjs +54 -0
  526. package/scripts/daemon/classify-kind.test.mjs +40 -0
  527. package/scripts/daemon/context-compiler.mjs +605 -0
  528. package/scripts/daemon/context-compiler.test.mjs +300 -0
  529. package/scripts/daemon/dispatcher-cooldown.test.mjs +122 -0
  530. package/scripts/daemon/dispatcher-governance.test.mjs +886 -0
  531. package/scripts/daemon/dispatcher.mjs +1516 -0
  532. package/scripts/daemon/health.mjs +72 -0
  533. package/scripts/daemon/inbox-deferral.mjs +210 -0
  534. package/scripts/daemon/inbox-deferral.test.mjs +242 -0
  535. package/scripts/daemon/integration.test.mjs +149 -0
  536. package/scripts/daemon/launchd-wrapper-generic.sh +96 -0
  537. package/scripts/daemon/launchd-wrapper-slack-events.sh +37 -0
  538. package/scripts/daemon/launchd-wrapper.sh +91 -0
  539. package/scripts/daemon/lib/session-router.mjs +274 -0
  540. package/scripts/daemon/lib/session-router.test.mjs +295 -0
  541. package/scripts/daemon/maestro-daemon.mjs +275 -0
  542. package/scripts/daemon/prompt-builder.mjs +685 -0
  543. package/scripts/daemon/prompt-builder.test.mjs +213 -0
  544. package/scripts/daemon/responder.mjs +854 -0
  545. package/scripts/daemon/session-lock.mjs +721 -0
  546. package/scripts/daemon/session-lock.test.mjs +252 -0
  547. package/scripts/daemon/session-outcomes.mjs +640 -0
  548. package/scripts/daemon/session-outcomes.test.mjs +533 -0
  549. package/scripts/daemon/typing-registry.mjs +90 -0
  550. package/scripts/daemon/typing-registry.test.mjs +77 -0
  551. package/scripts/daemon/voice-webhook-server.mjs +804 -0
  552. package/scripts/decisions/capture-decision.mjs +116 -0
  553. package/scripts/disclosure_assessment.py +873 -0
  554. package/scripts/disclosure_boundaries.py +562 -0
  555. package/scripts/email-signature-principal.html +52 -0
  556. package/scripts/email-signature.html +60 -0
  557. package/scripts/email_quote_thread.py +167 -0
  558. package/scripts/email_thread_dedup.py +362 -0
  559. package/scripts/emergency-stop.sh +81 -0
  560. package/scripts/healthcheck.sh +116 -0
  561. package/scripts/hooks/block-mcp-cohort-send.sh +15 -0
  562. package/scripts/hooks/block-mcp-slack-send.sh +7 -0
  563. package/scripts/hooks/post-action-log.sh +126 -0
  564. package/scripts/hooks/pre-send-audit.sh +174 -0
  565. package/scripts/hooks/pre-send-audit.test.mjs +215 -0
  566. package/scripts/hooks/session-end-log.sh +27 -0
  567. package/scripts/hooks/session-start-banner.sh +115 -0
  568. package/scripts/huddle/audio-bridge.mjs +664 -0
  569. package/scripts/huddle/boot-slack-cdp.sh +102 -0
  570. package/scripts/huddle/huddle-controller.mjs +942 -0
  571. package/scripts/huddle/huddle-server.mjs +1229 -0
  572. package/scripts/huddle/launch-slack.sh +232 -0
  573. package/scripts/huddle/openai-realtime-bridge.mjs +462 -0
  574. package/scripts/huddle/package-lock.json +62 -0
  575. package/scripts/huddle/package.json +22 -0
  576. package/scripts/huddle/setup-audio.sh +239 -0
  577. package/scripts/huddle/start-call.mjs +318 -0
  578. package/scripts/huddle/test-pipeline.mjs +263 -0
  579. package/scripts/learning/consolidate-skills.mjs +72 -0
  580. package/scripts/learning/session-search.mjs +125 -0
  581. package/scripts/llm_email_dedup.py +442 -0
  582. package/scripts/local-triggers/generate-plists.sh +432 -0
  583. package/scripts/local-triggers/generate-plists.test.mjs +413 -0
  584. package/scripts/local-triggers/install-all.sh +49 -0
  585. package/scripts/local-triggers/plists/.gitkeep +0 -0
  586. package/scripts/local-triggers/run-trigger.sh +63 -0
  587. package/scripts/local-triggers/templates/rag-reindex.plist.template +47 -0
  588. package/scripts/local-triggers/templates/voice-relay-poller.plist.template +54 -0
  589. package/scripts/local-triggers/templates/voice-tunnel.plist.template +55 -0
  590. package/scripts/local-triggers/templates/voice-webhook.plist.template +51 -0
  591. package/scripts/maintenance/backup-to-cloud.sh +124 -0
  592. package/scripts/maintenance/health-check.sh +377 -0
  593. package/scripts/media-generation/README.md +105 -0
  594. package/scripts/media-generation/gemini-image-client.mjs +173 -0
  595. package/scripts/media-generation/generate-assets.mjs +289 -0
  596. package/scripts/media-generation/veo-video-client.mjs +219 -0
  597. package/scripts/org/send-orgmail.mjs +227 -0
  598. package/scripts/outbound-dedup-cleanup.sh +43 -0
  599. package/scripts/outbound-dedup.sh +477 -0
  600. package/scripts/outbound_dedup.py +115 -0
  601. package/scripts/parse-voice-transcript.mjs +481 -0
  602. package/scripts/pdf-generation/README.md +63 -0
  603. package/scripts/pdf-generation/build-document.mjs +247 -0
  604. package/scripts/pdf-generation/templates/board-pack.latex +136 -0
  605. package/scripts/pdf-generation/templates/corporate-letter.latex +126 -0
  606. package/scripts/pdf-generation/templates/memo.latex +114 -0
  607. package/scripts/poll-slack-events.sh +35 -0
  608. package/scripts/poller/calendar-poller.mjs +12 -0
  609. package/scripts/poller/gmail-poller.mjs +192 -0
  610. package/scripts/poller/imap-client.mjs +289 -0
  611. package/scripts/poller/inbox-scan-poller.mjs +156 -0
  612. package/scripts/poller/inbox-scan-poller.test.mjs +231 -0
  613. package/scripts/poller/index.mjs +73 -0
  614. package/scripts/poller/intra-session-check.mjs +285 -0
  615. package/scripts/poller/lib/cloud-relay-dedup.mjs +88 -0
  616. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +133 -0
  617. package/scripts/poller/lib/slash-command-handlers.mjs +177 -0
  618. package/scripts/poller/secondary-gmail-poller.mjs +132 -0
  619. package/scripts/poller/slack-cloud-relay-client.mjs +368 -0
  620. package/scripts/poller/slack-poller.mjs +854 -0
  621. package/scripts/poller/slack-socket-mode.mjs +917 -0
  622. package/scripts/poller/slack-socket-mode.test.mjs +753 -0
  623. package/scripts/poller/trigger.mjs +75 -0
  624. package/scripts/poller/utils.mjs +371 -0
  625. package/scripts/poller/voice-cloud-relay-client.mjs +179 -0
  626. package/scripts/poller/voice-poller.mjs +236 -0
  627. package/scripts/poller-launchd/install.sh +66 -0
  628. package/scripts/poller-launchd/poller.plist.template +40 -0
  629. package/scripts/poller-launchd/whatsapp-handler.plist.template +39 -0
  630. package/scripts/post-interaction-indexer.py +1598 -0
  631. package/scripts/pre-draft-context.py +994 -0
  632. package/scripts/pre_draft_lookup.py +258 -0
  633. package/scripts/rag/build-index.mjs +47 -0
  634. package/scripts/rag/ingest.mjs +111 -0
  635. package/scripts/rag/search.mjs +119 -0
  636. package/scripts/rag-indexer.py +629 -0
  637. package/scripts/restore-from-backup.sh +248 -0
  638. package/scripts/restore-from-backup.test.mjs +178 -0
  639. package/scripts/resume-operations.sh +80 -0
  640. package/scripts/search-secondary-inbox.py +181 -0
  641. package/scripts/secondary-inbox-poller.py +437 -0
  642. package/scripts/self-optimization/compute-metrics.py +398 -0
  643. package/scripts/send-email-as-principal.py +369 -0
  644. package/scripts/send-email-threaded.py +392 -0
  645. package/scripts/send-email-with-attachment.py +377 -0
  646. package/scripts/send-email.sh +131 -0
  647. package/scripts/send-sms.sh +175 -0
  648. package/scripts/send-whatsapp.sh +292 -0
  649. package/scripts/session-start.sh +106 -0
  650. package/scripts/setup/boot-claude-session.sh +94 -0
  651. package/scripts/setup/configure-macos.sh +674 -0
  652. package/scripts/setup/configure-twilio-sip-trunk.mjs +207 -0
  653. package/scripts/setup/configure-voice-tunnel.mjs +182 -0
  654. package/scripts/setup/generate-agent-env.mjs +92 -0
  655. package/scripts/setup/generate-agent-package-json.mjs +222 -0
  656. package/scripts/setup/generate-agent-package-json.test.mjs +143 -0
  657. package/scripts/setup/generate-autonomy.mjs +60 -0
  658. package/scripts/setup/generate-backlog.mjs +101 -0
  659. package/scripts/setup/generate-cadences.mjs +92 -0
  660. package/scripts/setup/generate-capability.mjs +162 -0
  661. package/scripts/setup/generate-charter.mjs +76 -0
  662. package/scripts/setup/generate-comms.mjs +59 -0
  663. package/scripts/setup/generate-company.mjs +92 -0
  664. package/scripts/setup/init-agent.sh +547 -0
  665. package/scripts/setup/init-agent.test.mjs +151 -0
  666. package/scripts/setup/init-archetype.mjs +74 -0
  667. package/scripts/setup/init-backup.mjs +54 -0
  668. package/scripts/setup/init-cadence-bus.mjs +60 -0
  669. package/scripts/setup/init-channel-bus.mjs +46 -0
  670. package/scripts/setup/init-cost-tracking.mjs +45 -0
  671. package/scripts/setup/init-decision-capture.mjs +66 -0
  672. package/scripts/setup/init-known-agents.mjs +57 -0
  673. package/scripts/setup/init-learning.mjs +70 -0
  674. package/scripts/setup/init-memory-executive.mjs +45 -0
  675. package/scripts/setup/init-model-router.mjs +124 -0
  676. package/scripts/setup/init-rag.mjs +174 -0
  677. package/scripts/setup/init-session-router.mjs +38 -0
  678. package/scripts/setup/init-slack-socket-mode.mjs +260 -0
  679. package/scripts/setup/init-telegram.mjs +165 -0
  680. package/scripts/setup/init-voice-realtime.mjs +204 -0
  681. package/scripts/setup/init-whatsapp-baileys.mjs +77 -0
  682. package/scripts/setup/install-dev-tools.sh +150 -0
  683. package/scripts/setup/lib/install-plist.mjs +95 -0
  684. package/scripts/setup/migrate-agent-to-sot.mjs +192 -0
  685. package/scripts/setup/render-environment-yaml.mjs +133 -0
  686. package/scripts/slack-events-ctl.sh +177 -0
  687. package/scripts/slack-events-server.mjs +1045 -0
  688. package/scripts/slack-react.mjs +89 -0
  689. package/scripts/slack-responded.sh +232 -0
  690. package/scripts/slack-send.sh +287 -0
  691. package/scripts/slack-typing.mjs +196 -0
  692. package/scripts/slack-upload-v2.py +95 -0
  693. package/scripts/sms-handler.mjs +450 -0
  694. package/scripts/spawn-session.sh +120 -0
  695. package/scripts/sync-protocol.mjs +217 -0
  696. package/scripts/system-verify.sh +184 -0
  697. package/scripts/test-email-thread-dedup.py +239 -0
  698. package/scripts/test-information-barriers.py +484 -0
  699. package/scripts/test-llm-email-dedup.py +251 -0
  700. package/scripts/test-pre-draft-integration.py +203 -0
  701. package/scripts/test-rag-phase2.sh +442 -0
  702. package/scripts/test-rag-search.sh +251 -0
  703. package/scripts/test-voice-parser.mjs +316 -0
  704. package/scripts/user-context-search.py +659 -0
  705. package/scripts/validate_outbound.py +1504 -0
  706. package/scripts/watchdog/ai.maestro.memory-watchdog.plist +41 -0
  707. package/scripts/watchdog/force-reboot.sh +157 -0
  708. package/scripts/watchdog/memory-watchdog.sh +473 -0
  709. package/scripts/whatsapp-handler.mjs +538 -0
  710. package/teams/desktop-operations.yaml +34 -0
  711. package/teams/executive-office.yaml +27 -0
  712. package/teams/legal-and-regulatory.yaml +24 -0
  713. package/teams/platform-and-engineering.yaml +23 -0
  714. package/teams/strategy-and-growth.yaml +29 -0
  715. package/workflows/continuous/backlog-executor.yaml +141 -0
  716. package/workflows/continuous/inbound-monitor.yaml +168 -0
  717. package/workflows/daily/applicant-triage.yaml +197 -0
  718. package/workflows/daily/comms-triage.yaml +80 -0
  719. package/workflows/daily/evening-wrap.yaml +105 -0
  720. package/workflows/daily/morning-brief.yaml +164 -0
  721. package/workflows/daily/slack-followup-sweep.yaml +87 -0
  722. package/workflows/event-driven/README.md +50 -0
  723. package/workflows/event-driven/agent-failure-investigation.yaml +137 -0
  724. package/workflows/event-driven/pr-review.yaml +107 -0
  725. package/workflows/monthly/board-readiness.yaml +76 -0
  726. package/workflows/quarterly/strategic-scenario-analysis.yaml +85 -0
  727. package/workflows/session-protocol.md +171 -0
  728. package/workflows/weekly/engineering-health.yaml +154 -0
  729. package/workflows/weekly/hiring-review.yaml +169 -0
  730. package/workflows/weekly/rollup-pipeline-review.yaml +76 -0
  731. package/workflows/weekly/strategic-memo.yaml +79 -0
@@ -0,0 +1,475 @@
1
+ /**
2
+ * Maestro — RAG search (sqlite-fts5)
3
+ *
4
+ * Query the indexed agent corpus and return top-N matching chunks with
5
+ * BM25 scores and snippet text. Used by:
6
+ * - `rag_search` tool exposed to voice/Claude sessions
7
+ * - voice context-loader for at-call pre-loading of relevant chunks
8
+ * - daemon's classifier-side lookups when full-context is overkill
9
+ *
10
+ * Public API:
11
+ * search({ query, topN?, kind?, agentRoot? }) →
12
+ * [{ source_path, kind, title, heading, snippet, score, relevance }]
13
+ *
14
+ * BM25 score range (SQLite FTS5 `bm25()`): NEGATIVE, where a more-negative
15
+ * value means a better match (0 is the worst possible — no term overlap).
16
+ * `ORDER BY bm25(chunks)` ascending therefore puts the best hit first. We
17
+ * expose a normalized `relevance` field in [0,1] (1.0 = top hit in this
18
+ * result set, → 0 = weakest) so callers don't need to interpret the raw,
19
+ * sign-inverted BM25 value directly. See `ftsRelevance()` for the transform.
20
+ */
21
+
22
+ import { existsSync } from "node:fs";
23
+ import { join, resolve } from "node:path";
24
+
25
+ let _Database = null;
26
+ async function loadDatabase() {
27
+ if (_Database) return _Database;
28
+ const mod = await import("better-sqlite3");
29
+ _Database = mod.default || mod;
30
+ return _Database;
31
+ }
32
+
33
+ const FTS_DB_REL = "state/rag/index/agent.db";
34
+
35
+ // Stop-word list: common English words that add noise to BM25 ranking
36
+ // without carrying meaning. Anything < 3 chars is also dropped.
37
+ const STOPWORDS = new Set([
38
+ "the", "and", "for", "are", "but", "not", "you", "all", "any", "can",
39
+ "had", "her", "was", "one", "our", "out", "day", "get", "has", "him",
40
+ "his", "how", "man", "new", "now", "old", "see", "two", "way", "who",
41
+ "boy", "did", "its", "let", "put", "say", "she", "too", "use",
42
+ "with", "have", "this", "that", "from", "will", "what", "your", "when",
43
+ "been", "were", "they", "them", "than", "then", "would", "could",
44
+ "should", "about", "which", "their", "there", "where", "while",
45
+ "into", "onto", "upon", "over", "under", "after", "before", "between",
46
+ ]);
47
+
48
+ /**
49
+ * Tokenize + sanitize user input into FTS5-safe MATCH tokens. Strategy:
50
+ * 1. Lowercase + strip non-alphanumeric → bare tokens.
51
+ * 2. Drop stop-words and tokens < 3 chars (they're noise for BM25).
52
+ * 3. Take the top ~16 tokens by length (longer = more specific).
53
+ * 4. Each token is double-quoted (with any embedded quotes stripped) so we
54
+ * never pass an FTS5 operator (NEAR, NOT, OR, AND, *, …) from user input.
55
+ *
56
+ * Returns the array of quoted tokens (e.g. `["\"alpha\"", "\"beta\""]`) so
57
+ * callers can choose the boolean joiner — see `buildMatch()` for AND-first /
58
+ * OR-fallback. Returns `[]` when nothing survives sanitization.
59
+ */
60
+ function tokenizeQuery(q) {
61
+ const all = String(q || "")
62
+ .toLowerCase()
63
+ .replace(/[^\p{L}\p{N}\s]/gu, " ")
64
+ .split(/\s+/)
65
+ .filter((t) => t.length >= 3 && !STOPWORDS.has(t));
66
+ if (!all.length) return [];
67
+ // Dedup + prefer specific terms (longer first), cap at 16.
68
+ const ranked = Array.from(new Set(all)).sort((a, b) => b.length - a.length).slice(0, 16);
69
+ return ranked.map((t) => `"${t.replace(/"/g, "")}"`);
70
+ }
71
+
72
+ /**
73
+ * Build an FTS5 MATCH expression from sanitized tokens.
74
+ * - mode "and": require every token (`"a" AND "b"`). High precision; used
75
+ * first for short multi-term queries so a doc matching ALL terms ranks
76
+ * above one matching just a common term (audit L14).
77
+ * - mode "or": any token may match (`"a" OR "b"`). Used as the zero-hit
78
+ * fallback so a strict AND never silently returns nothing.
79
+ * A single token is identical under both modes.
80
+ */
81
+ function buildMatch(tokens, mode) {
82
+ if (!tokens.length) return null;
83
+ return tokens.join(mode === "or" ? " OR " : " AND ");
84
+ }
85
+
86
+ /**
87
+ * Back-compat shim: the original API returned an OR-joined MATCH string.
88
+ * Kept so any external caller importing the old behaviour still works;
89
+ * internal callers use `tokenizeQuery` + `buildMatch` directly.
90
+ */
91
+ function escapeQuery(q) {
92
+ const tokens = tokenizeQuery(q);
93
+ return tokens.length ? buildMatch(tokens, "or") : null;
94
+ }
95
+
96
+ /**
97
+ * @typedef {object} RagHit
98
+ * @property {string} source_path
99
+ * @property {string} kind
100
+ * @property {string} title
101
+ * @property {string|null} heading
102
+ * @property {string} snippet
103
+ * @property {number} score BM25 score (negative; lower = better) — kept for compat
104
+ * @property {number} relevance Normalized 0..1 (1.0 = best)
105
+ * @property {string} [retrieval] "fts" | "semantic" | "hybrid"
106
+ * @property {number} [bm25_rank] Rank in FTS5 results (1-based)
107
+ * @property {number} [semantic_rank] Rank in semantic results (1-based)
108
+ * @property {number} [semantic_score] Raw cosine similarity (0..1)
109
+ */
110
+
111
+ /**
112
+ * Search the indexed corpus.
113
+ *
114
+ * Modes (auto-detected unless `mode` is set):
115
+ * - "fts" Pure BM25 keyword search (default if no embeddings available
116
+ * or `mode: "fts"` is set explicitly). Fast, free, exact terms.
117
+ * - "semantic" Pure cosine similarity over embeddings. Best for paraphrase
118
+ * queries ("anything about culture last quarter") where the
119
+ * exact terms aren't in the corpus.
120
+ * - "hybrid" Run both, fuse via Reciprocal Rank Fusion. Default when
121
+ * embeddings are available — best of both worlds.
122
+ *
123
+ * @param {object} opts
124
+ * @param {string} opts.query User query (free-text)
125
+ * @param {number} [opts.topN] Max results (default: 8)
126
+ * @param {string} [opts.kind] Filter by chunk.kind
127
+ * @param {string} [opts.agentRoot]
128
+ * @param {boolean} [opts.raw] For "fts" mode: trust caller's MATCH syntax
129
+ * @param {"fts"|"semantic"|"hybrid"} [opts.mode]
130
+ * @param {string} [opts.embedModel] Override embedding model for query
131
+ * @returns {Promise<Array<RagHit>>}
132
+ */
133
+ export async function search(opts = {}) {
134
+ if (!opts.query) return [];
135
+ const Database = await loadDatabase();
136
+ const root = resolve(opts.agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd());
137
+ const dbPath = join(root, FTS_DB_REL);
138
+ if (!existsSync(dbPath)) return [];
139
+
140
+ const topN = Math.min(Math.max(parseInt(opts.topN, 10) || 8, 1), 50);
141
+ const db = new Database(dbPath, { readonly: true });
142
+ try {
143
+ // Detect whether embeddings are available + decide mode.
144
+ const hasEmbeds = db.prepare("SELECT COUNT(*) AS n FROM embeddings").get().n > 0;
145
+ let mode = opts.mode || (hasEmbeds && process.env.OPENAI_API_KEY ? "hybrid" : "fts");
146
+ if (mode !== "fts" && !hasEmbeds) mode = "fts";
147
+
148
+ if (mode === "fts") return runFts(db, opts, topN);
149
+ if (mode === "semantic") return await runSemantic(db, opts, topN);
150
+ return await runHybrid(db, opts, topN);
151
+ } finally { db.close(); }
152
+ }
153
+
154
+ // ---------------------------------------------------------------------------
155
+ // FTS-only path
156
+ // ---------------------------------------------------------------------------
157
+
158
+ /**
159
+ * Map a row's raw FTS5 bm25() score to a faithful [0,1] relevance, given the
160
+ * best (most-negative) score in the same result set (audit L15).
161
+ *
162
+ * FTS5 bm25() is NEGATIVE — more negative = better — and 0 means "no overlap".
163
+ * The old `bestScore / r.score` ratio silently assumed a POSITIVE BM25, so it
164
+ * inverted the ordering for the (negative) real values and, when `r.score` was
165
+ * 0, produced Infinity before clamping. Here we anchor relevance at the best
166
+ * score: the top hit is exactly 1.0, weaker (less-negative, larger) scores
167
+ * decay toward 0, and a 0 score (no overlap) maps to 0. All paths are finite —
168
+ * there is no division that can hit 0 or produce Inf/NaN.
169
+ *
170
+ * Exported (additively) so the offline test suite can assert the zero-score
171
+ * and out-of-range guards directly without a contrived FTS row.
172
+ *
173
+ * @param {number} score this row's bm25() value (≤ 0)
174
+ * @param {number} bestScore most-negative bm25() in the set (≤ 0)
175
+ * @returns {number} relevance in [0,1]
176
+ */
177
+ export function ftsRelevance(score, bestScore) {
178
+ const s = Number(score);
179
+ const best = Number(bestScore);
180
+ if (!Number.isFinite(s) || !Number.isFinite(best)) return 0;
181
+ // Best score 0 means even the top hit has no measurable overlap → no signal.
182
+ if (best === 0) return 0;
183
+ // A row's own score of 0 (no overlap) maps to 0.
184
+ if (s === 0) return 0;
185
+ // Both s and best are < 0 here, and best is the most-negative (best) score,
186
+ // so |s| ≤ |best| and ratio = s / best ∈ (0, 1]: the top hit (s === best) is
187
+ // exactly 1.0, and a weaker (less-negative, i.e. closer-to-0) score yields a
188
+ // smaller fraction. (Using best / s would invert this and make weak hits look
189
+ // perfect.) The clamp is a belt-and-braces guard against FP edge cases.
190
+ const ratio = s / best;
191
+ if (!Number.isFinite(ratio)) return 0;
192
+ return Math.max(0, Math.min(1, ratio));
193
+ }
194
+
195
+ function runFts(db, opts, topN) {
196
+ if (opts.raw) {
197
+ return queryFts(db, opts.query, opts, topN);
198
+ }
199
+ const tokens = tokenizeQuery(opts.query);
200
+ if (!tokens.length) return [];
201
+ // AND-first for precision on short multi-term queries; fall back to OR only
202
+ // when the strict conjunction returns nothing (audit L14).
203
+ const andMatch = buildMatch(tokens, "and");
204
+ let rows = queryFts(db, andMatch, opts, topN);
205
+ if (rows.length === 0 && tokens.length > 1) {
206
+ const orMatch = buildMatch(tokens, "or");
207
+ rows = queryFts(db, orMatch, opts, topN);
208
+ }
209
+ return rows;
210
+ }
211
+
212
+ /** Execute one FTS5 MATCH and shape the rows (incl. faithful relevance). */
213
+ function queryFts(db, matchQuery, opts, topN) {
214
+ if (!matchQuery) return [];
215
+ let sql = `
216
+ SELECT
217
+ rowid,
218
+ body,
219
+ source_path,
220
+ kind,
221
+ title,
222
+ heading,
223
+ bm25(chunks) AS score,
224
+ snippet(chunks, 0, '«', '»', '…', 32) AS snippet
225
+ FROM chunks
226
+ WHERE chunks MATCH ?
227
+ `;
228
+ const params = [matchQuery];
229
+ if (opts.kind) { sql += " AND kind = ?"; params.push(opts.kind); }
230
+ sql += " ORDER BY bm25(chunks) LIMIT ?";
231
+ params.push(topN);
232
+
233
+ const rows = db.prepare(sql).all(...params);
234
+ if (rows.length === 0) return [];
235
+ const bestScore = rows[0].score; // most-negative (best) thanks to ORDER BY
236
+ return rows.map((r, i) => ({
237
+ source_path: r.source_path,
238
+ kind: r.kind,
239
+ title: r.title,
240
+ heading: r.heading || null,
241
+ snippet: r.snippet,
242
+ score: r.score,
243
+ relevance: ftsRelevance(r.score, bestScore),
244
+ retrieval: "fts",
245
+ bm25_rank: i + 1,
246
+ }));
247
+ }
248
+
249
+ // ---------------------------------------------------------------------------
250
+ // Semantic path — cosine similarity over all stored embeddings
251
+ // ---------------------------------------------------------------------------
252
+
253
+ // Candidate ceiling for the brute-force cosine scan. Above this many
254
+ // matching-(model,dim) embeddings we cap the scan and warn rather than
255
+ // pulling the entire BLOB column into JS (audit H11). Override via
256
+ // RAG_MAX_SEMANTIC_CANDIDATES. Normal-size corpora (< ceiling) are
257
+ // scored in full and behave exactly as before.
258
+ export const DEFAULT_MAX_SEMANTIC_CANDIDATES = 50000;
259
+
260
+ function maxSemanticCandidates() {
261
+ const n = parseInt(process.env.RAG_MAX_SEMANTIC_CANDIDATES, 10);
262
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_MAX_SEMANTIC_CANDIDATES;
263
+ }
264
+
265
+ async function runSemantic(db, opts, topN) {
266
+ const embedMod = await import("./embed.mjs");
267
+ // Resolve the query embedding AND the model/dim it was produced with, so
268
+ // we can constrain scoring to compatible embeddings only (audit M12). A
269
+ // re-index under a new embedModel leaves old-dim rows whose cosine would
270
+ // be 0 on length mismatch — silently collapsing recall. Filtering on
271
+ // (model, dim) makes that impossible instead of relying on a 0 coincidence.
272
+ let queryVec, queryModel;
273
+ if (typeof opts._embedQuery === "function") {
274
+ // Offline/test injection seam: returns { vector, model } or a bare vector.
275
+ const r = await opts._embedQuery(opts.query, { model: opts.embedModel });
276
+ if (r && r.vector) { queryVec = r.vector; queryModel = r.model; }
277
+ else { queryVec = r; queryModel = opts.embedModel; }
278
+ } else {
279
+ const r = await embedMod.embedTexts([opts.query || ""], { model: opts.embedModel });
280
+ queryVec = r.vectors[0];
281
+ queryModel = r.model;
282
+ }
283
+ if (!queryVec) return [];
284
+ const queryDim = queryVec.length;
285
+ if (!queryModel) queryModel = opts.embedModel || "text-embedding-3-small";
286
+
287
+ // Bind the (model, dim) filter (+ optional kind) on every embeddings read.
288
+ const filterSql = " WHERE e.model = ? AND e.dim = ?" + (opts.kind ? " AND c.kind = ?" : "");
289
+ const filterParams = opts.kind ? [queryModel, queryDim, opts.kind] : [queryModel, queryDim];
290
+
291
+ // Enforce a documented candidate ceiling (audit H11): count first, and if
292
+ // the compatible-embedding count exceeds the ceiling, cap the scan with a
293
+ // LIMIT and warn rather than risk OOM on an unbounded BLOB pull.
294
+ const ceiling = maxSemanticCandidates();
295
+ const candidateCount = db.prepare(
296
+ "SELECT COUNT(*) AS n FROM chunks c JOIN embeddings e ON e.chunk_rowid = c.rowid" + filterSql
297
+ ).get(...filterParams).n;
298
+ if (candidateCount === 0) return [];
299
+ let limitClause = "";
300
+ const scanParams = [...filterParams];
301
+ if (candidateCount > ceiling) {
302
+ console.warn(
303
+ `[rag] semantic candidate set ${candidateCount} exceeds ceiling ${ceiling}; ` +
304
+ `capping scan (set RAG_MAX_SEMANTIC_CANDIDATES to raise, or add an ANN index).`
305
+ );
306
+ limitClause = " LIMIT ?";
307
+ scanParams.push(ceiling);
308
+ }
309
+
310
+ // Scoring SELECT deliberately omits c.body (audit H11): bodies are large
311
+ // and we only need them for the final topN, fetched by rowid below.
312
+ const rows = db.prepare(
313
+ "SELECT c.rowid, c.source_path, c.kind, c.title, c.heading, e.vector " +
314
+ "FROM chunks c JOIN embeddings e ON e.chunk_rowid = c.rowid" + filterSql + limitClause
315
+ ).all(...scanParams);
316
+ if (rows.length === 0) return [];
317
+
318
+ // Brute-force cosine over the (now body-free) candidate rows.
319
+ const scored = rows.map((r) => {
320
+ const v = embedMod.deserializeVector(r.vector);
321
+ return {
322
+ rowid: r.rowid,
323
+ source_path: r.source_path,
324
+ kind: r.kind,
325
+ title: r.title,
326
+ heading: r.heading || null,
327
+ similarity: embedMod.cosineSimilarity(queryVec, v),
328
+ };
329
+ });
330
+ scored.sort((a, b) => b.similarity - a.similarity);
331
+ const top = scored.slice(0, topN);
332
+
333
+ // Fetch bodies ONLY for the final topN (by rowid) to build snippets.
334
+ const bodyById = new Map();
335
+ if (top.length) {
336
+ const placeholders = top.map(() => "?").join(",");
337
+ for (const br of db.prepare(
338
+ `SELECT rowid, body FROM chunks WHERE rowid IN (${placeholders})`
339
+ ).all(...top.map((r) => r.rowid))) {
340
+ bodyById.set(br.rowid, br.body);
341
+ }
342
+ }
343
+
344
+ return top.map((r, i) => ({
345
+ source_path: r.source_path,
346
+ kind: r.kind,
347
+ title: r.title,
348
+ heading: r.heading,
349
+ snippet: bareSnippet(bodyById.get(r.rowid) || "", opts.query),
350
+ score: r.similarity, // for compat
351
+ relevance: Math.max(0.01, Math.min(1, (r.similarity - top[top.length - 1].similarity) /
352
+ (top[0].similarity - top[top.length - 1].similarity + 1e-9))),
353
+ retrieval: "semantic",
354
+ semantic_rank: i + 1,
355
+ semantic_score: r.similarity,
356
+ }));
357
+ }
358
+
359
+ // ---------------------------------------------------------------------------
360
+ // Hybrid path — Reciprocal Rank Fusion of BM25 + cosine rankings
361
+ // ---------------------------------------------------------------------------
362
+
363
+ const RRF_K = 60; // standard constant in the RRF literature (Cormack et al. 2009)
364
+ const FETCH_PER_LIST = 30; // pull more from each list than topN so the fusion has material to rank
365
+
366
+ async function runHybrid(db, opts, topN) {
367
+ // Run FTS5 first (cheap, in-process).
368
+ const ftsResults = runFts(db, { ...opts, topN: FETCH_PER_LIST }, FETCH_PER_LIST);
369
+ // And semantic in parallel.
370
+ let semanticResults = [];
371
+ try {
372
+ semanticResults = await runSemantic(db, { ...opts, topN: FETCH_PER_LIST }, FETCH_PER_LIST);
373
+ } catch { /* embeddings unavailable; we fall back to FTS-only below */ }
374
+
375
+ if (semanticResults.length === 0) return ftsResults.slice(0, topN);
376
+ if (ftsResults.length === 0) return semanticResults.slice(0, topN);
377
+
378
+ // Reciprocal Rank Fusion: each result's contribution from each list is
379
+ // 1 / (k + rank). Sum across lists, sort, take top N.
380
+ const fused = new Map(); // source_path|heading → aggregator
381
+ const keyOf = (r) => `${r.source_path}|${r.heading || ""}`;
382
+
383
+ for (let i = 0; i < ftsResults.length; i++) {
384
+ const r = ftsResults[i];
385
+ const k = keyOf(r);
386
+ fused.set(k, {
387
+ ...r,
388
+ rrf_score: 1 / (RRF_K + (i + 1)),
389
+ bm25_rank: i + 1,
390
+ semantic_rank: null,
391
+ semantic_score: null,
392
+ retrieval: "hybrid",
393
+ });
394
+ }
395
+ for (let i = 0; i < semanticResults.length; i++) {
396
+ const r = semanticResults[i];
397
+ const k = keyOf(r);
398
+ if (fused.has(k)) {
399
+ const f = fused.get(k);
400
+ f.rrf_score += 1 / (RRF_K + (i + 1));
401
+ f.semantic_rank = i + 1;
402
+ f.semantic_score = r.semantic_score;
403
+ } else {
404
+ fused.set(k, {
405
+ source_path: r.source_path,
406
+ kind: r.kind,
407
+ title: r.title,
408
+ heading: r.heading,
409
+ snippet: r.snippet,
410
+ score: r.score,
411
+ rrf_score: 1 / (RRF_K + (i + 1)),
412
+ bm25_rank: null,
413
+ semantic_rank: i + 1,
414
+ semantic_score: r.semantic_score,
415
+ retrieval: "hybrid",
416
+ });
417
+ }
418
+ }
419
+
420
+ const arr = Array.from(fused.values()).sort((a, b) => b.rrf_score - a.rrf_score).slice(0, topN);
421
+ if (arr.length === 0) return [];
422
+ const bestRrf = arr[0].rrf_score;
423
+ return arr.map((r) => ({
424
+ source_path: r.source_path,
425
+ kind: r.kind,
426
+ title: r.title,
427
+ heading: r.heading,
428
+ snippet: r.snippet,
429
+ score: r.score,
430
+ relevance: bestRrf === 0 ? 1 : Math.max(0.01, Math.min(1, r.rrf_score / bestRrf)),
431
+ retrieval: "hybrid",
432
+ bm25_rank: r.bm25_rank,
433
+ semantic_rank: r.semantic_rank,
434
+ semantic_score: r.semantic_score,
435
+ }));
436
+ }
437
+
438
+ /** Produce a snippet centered on the first query term that appears in the body. */
439
+ function bareSnippet(body, query) {
440
+ const text = String(body || "").replace(/\s+/g, " ").trim();
441
+ if (!text) return "";
442
+ const terms = String(query).toLowerCase().split(/\s+/).filter((t) => t.length > 3);
443
+ let idx = -1;
444
+ for (const t of terms) {
445
+ idx = text.toLowerCase().indexOf(t);
446
+ if (idx >= 0) break;
447
+ }
448
+ if (idx < 0) idx = 0;
449
+ const start = Math.max(0, idx - 60);
450
+ const end = Math.min(text.length, idx + 200);
451
+ return (start > 0 ? "…" : "") + text.slice(start, end) + (end < text.length ? "…" : "");
452
+ }
453
+
454
+ /**
455
+ * @returns {Promise<{ files: number, chunks: number, by_kind: object, db_size_bytes: number }>}
456
+ */
457
+ export async function stats(opts = {}) {
458
+ const Database = await loadDatabase();
459
+ const root = resolve(opts.agentRoot || process.env.AGENT_ROOT || process.cwd());
460
+ const dbPath = join(root, FTS_DB_REL);
461
+ if (!existsSync(dbPath)) return { files: 0, chunks: 0, by_kind: {}, db_size_bytes: 0 };
462
+ const { statSync } = await import("node:fs");
463
+ const db = new Database(dbPath, { readonly: true });
464
+ try {
465
+ const files = db.prepare("SELECT COUNT(*) AS n FROM files").get().n;
466
+ const chunks = db.prepare("SELECT COUNT(*) AS n FROM chunks").get().n;
467
+ const byKind = {};
468
+ for (const row of db.prepare("SELECT kind, COUNT(*) AS n FROM files GROUP BY kind").all()) {
469
+ byKind[row.kind] = row.n;
470
+ }
471
+ return { files, chunks, by_kind: byKind, db_size_bytes: statSync(dbPath).size };
472
+ } finally { db.close(); }
473
+ }
474
+
475
+ export default { search, stats };