@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,442 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # test-rag-phase2.sh — Tests for RAG Phase 2 (SQLite FTS5 backend)
5
+ #
6
+ # Tests:
7
+ # 1-3. Indexer: full index, stats, incremental re-index
8
+ # 4-5. SQLite search: CEO gets results, correct backend label
9
+ # 6-7. Ring-fencing: leadership cannot see CEO private DMs
10
+ # 8-9. Ring-fencing: unknown user sees only public scope
11
+ # 10. Ring-fencing: partner sees public + own data only
12
+ # 11. Fallback: sqlite backend falls back to grep when DB missing
13
+ # 12. User resolution works with sqlite backend
14
+ # 13. Text format works with sqlite backend
15
+ # 14. FTS5 search quality: multi-word queries return relevant results
16
+ # 15. Incremental indexing skips unchanged files
17
+ # 16. Phase 1 backward compatibility: grep backend still works
18
+ # 17. Ring-fencing DB-level: direct SQL confirms no scope leakage
19
+ # 18. Indexer --stats runs without error
20
+ # 19. Custom --db path works
21
+ # 20. Empty query handling
22
+ #
23
+ # Usage: ./scripts/test-rag-phase2.sh
24
+
25
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
26
+ BASE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
27
+ SEARCH_SCRIPT="${SCRIPT_DIR}/user-context-search.py"
28
+ INDEXER_SCRIPT="${SCRIPT_DIR}/rag-indexer.py"
29
+ DB_PATH="${BASE_DIR}/state/rag/search.db"
30
+ TEST_DB="/tmp/test-rag-phase2.db"
31
+ PASS=0
32
+ FAIL=0
33
+ TOTAL=0
34
+
35
+ # ── Helpers ────────────────────────────────────────────────────────────────────
36
+
37
+ pass_test() {
38
+ PASS=$((PASS + 1))
39
+ TOTAL=$((TOTAL + 1))
40
+ echo " PASS: $1"
41
+ }
42
+
43
+ fail_test() {
44
+ FAIL=$((FAIL + 1))
45
+ TOTAL=$((TOTAL + 1))
46
+ echo " FAIL: $1"
47
+ if [ -n "${2:-}" ]; then
48
+ echo " Detail: $2"
49
+ fi
50
+ }
51
+
52
+ run_search() {
53
+ python3 "$SEARCH_SCRIPT" "$@" 2>/dev/null
54
+ }
55
+
56
+ run_indexer() {
57
+ python3 "$INDEXER_SCRIPT" "$@" 2>/dev/null
58
+ }
59
+
60
+ # ── Setup ──────────────────────────────────────────────────────────────────────
61
+
62
+ echo "================================================================"
63
+ echo "RAG Phase 2 — SQLite FTS5 backend test suite"
64
+ echo "================================================================"
65
+ echo ""
66
+
67
+ # Clean up test DB if it exists
68
+ rm -f "$TEST_DB"
69
+
70
+ # ── Test 1: Full index builds database ────────────────────────────────────────
71
+
72
+ echo "Test 1: Full index builds database"
73
+ OUTPUT=$(run_indexer --full --db "$TEST_DB" 2>&1 || true)
74
+
75
+ if [ -f "$TEST_DB" ]; then
76
+ pass_test "Database file created at $TEST_DB"
77
+ else
78
+ fail_test "Database file not created" "$OUTPUT"
79
+ fi
80
+
81
+ if echo "$OUTPUT" | grep -q "Indexed.*files"; then
82
+ pass_test "Indexer reported indexed files"
83
+ else
84
+ fail_test "Indexer should report indexed file count" "$OUTPUT"
85
+ fi
86
+
87
+ echo ""
88
+
89
+ # ── Test 2: Database has documents ────────────────────────────────────────────
90
+
91
+ echo "Test 2: Database contains documents"
92
+ DOC_COUNT=$(python3 -c "
93
+ import sqlite3
94
+ conn = sqlite3.connect('$TEST_DB')
95
+ count = conn.execute('SELECT COUNT(*) FROM documents').fetchone()[0]
96
+ print(count)
97
+ conn.close()
98
+ " 2>/dev/null || echo "0")
99
+
100
+ if [ "$DOC_COUNT" -gt 0 ] 2>/dev/null; then
101
+ pass_test "Database contains $DOC_COUNT documents"
102
+ else
103
+ fail_test "Database should contain documents (got $DOC_COUNT)"
104
+ fi
105
+
106
+ echo ""
107
+
108
+ # ── Test 3: FTS5 virtual table exists ─────────────────────────────────────────
109
+
110
+ echo "Test 3: FTS5 virtual table exists and is searchable"
111
+ FTS_RESULT=$(python3 -c "
112
+ import sqlite3
113
+ conn = sqlite3.connect('$TEST_DB')
114
+ try:
115
+ rows = conn.execute(\"SELECT COUNT(*) FROM documents_fts WHERE documents_fts MATCH '\\\"northwind\\\"'\").fetchone()[0]
116
+ print(rows)
117
+ except Exception as e:
118
+ print(f'ERROR: {e}')
119
+ conn.close()
120
+ " 2>/dev/null || echo "ERROR")
121
+
122
+ if echo "$FTS_RESULT" | grep -qv "ERROR"; then
123
+ if [ "$FTS_RESULT" -gt 0 ] 2>/dev/null; then
124
+ pass_test "FTS5 table is searchable, found $FTS_RESULT matches for 'northwind'"
125
+ else
126
+ fail_test "FTS5 search returned 0 results for 'northwind'"
127
+ fi
128
+ else
129
+ fail_test "FTS5 table query failed: $FTS_RESULT"
130
+ fi
131
+
132
+ echo ""
133
+
134
+ # ── Test 4: SQLite backend — CEO search returns results ──────────────────────
135
+
136
+ echo "Test 4: SQLite backend — CEO search returns results"
137
+ RESULT=$(run_search --user alex --query "the regulator" --backend sqlite --db "$TEST_DB" --format json 2>&1 || true)
138
+
139
+ MATCH_COUNT=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result_count'])" 2>/dev/null || echo "0")
140
+ if [ "$MATCH_COUNT" -gt 0 ] 2>/dev/null; then
141
+ pass_test "CEO sqlite search returned $MATCH_COUNT results for 'the regulator'"
142
+ else
143
+ fail_test "CEO sqlite search should return results for 'the regulator'"
144
+ fi
145
+
146
+ echo ""
147
+
148
+ # ── Test 5: SQLite backend label in output ────────────────────────────────────
149
+
150
+ echo "Test 5: Backend label is 'sqlite' in JSON output"
151
+ BACKEND=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('backend',''))" 2>/dev/null || echo "")
152
+ if [ "$BACKEND" = "sqlite" ]; then
153
+ pass_test "Backend reported as 'sqlite'"
154
+ else
155
+ fail_test "Backend should be 'sqlite', got '$BACKEND'"
156
+ fi
157
+
158
+ echo ""
159
+
160
+ # ── Test 6: Ring-fencing — leadership cannot see CEO private DMs ──────────────
161
+
162
+ echo "Test 6: Ring-fencing — leadership cannot see CEO private DMs"
163
+ RESULT=$(run_search --user jordan --query "alex" --backend sqlite --db "$TEST_DB" --format json --max-results 50 2>&1 || true)
164
+
165
+ LEAKED=$(echo "$RESULT" | python3 -c "
166
+ import sys, json
167
+ d = json.load(sys.stdin)
168
+ # Check if any result has user_scope of a private user (alex-chen)
169
+ leaked = [m for m in d['matches'] if m.get('user_scope') == 'alex-chen']
170
+ print(len(leaked))
171
+ " 2>/dev/null || echo "-1")
172
+
173
+ if [ "$LEAKED" = "0" ]; then
174
+ pass_test "Leadership user sees 0 of Alex's private documents"
175
+ else
176
+ fail_test "Leadership user should not see Alex's private docs (saw $LEAKED)"
177
+ fi
178
+
179
+ echo ""
180
+
181
+ # ── Test 7: Ring-fencing — CEO can see private DMs ────────────────────────────
182
+
183
+ echo "Test 7: Ring-fencing — CEO can see private DMs"
184
+ RESULT=$(run_search --user alex --query "voice" --backend sqlite --db "$TEST_DB" --format json --max-results 50 2>&1 || true)
185
+
186
+ PRIVATE_COUNT=$(echo "$RESULT" | python3 -c "
187
+ import sys, json
188
+ d = json.load(sys.stdin)
189
+ private = [m for m in d['matches'] if m.get('user_scope') == 'alex-chen']
190
+ print(len(private))
191
+ " 2>/dev/null || echo "0")
192
+
193
+ if [ "$PRIVATE_COUNT" -gt 0 ] 2>/dev/null; then
194
+ pass_test "CEO can see $PRIVATE_COUNT private documents"
195
+ else
196
+ fail_test "CEO should be able to see private DM documents"
197
+ fi
198
+
199
+ echo ""
200
+
201
+ # ── Test 8: Ring-fencing — unknown user sees only public ─────────────────────
202
+
203
+ echo "Test 8: Ring-fencing — unknown user sees only public scope"
204
+ RESULT=$(run_search --user nobody --query "northwind" --backend sqlite --db "$TEST_DB" --format json --max-results 50 2>&1 || true)
205
+
206
+ NON_PUBLIC=$(echo "$RESULT" | python3 -c "
207
+ import sys, json
208
+ d = json.load(sys.stdin)
209
+ non_pub = [m for m in d['matches'] if m.get('user_scope') != 'public']
210
+ print(len(non_pub))
211
+ " 2>/dev/null || echo "-1")
212
+
213
+ if [ "$NON_PUBLIC" = "0" ]; then
214
+ pass_test "Unknown user sees only public-scoped documents"
215
+ else
216
+ fail_test "Unknown user should only see public docs (saw $NON_PUBLIC non-public)"
217
+ fi
218
+
219
+ echo ""
220
+
221
+ # ── Test 9: Ring-fencing — unknown user access level is default ──────────────
222
+
223
+ echo "Test 9: Unknown user has 'default' access level"
224
+ ACCESS=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_level'])" 2>/dev/null || echo "")
225
+ if [ "$ACCESS" = "default" ]; then
226
+ pass_test "Unknown user access_level is 'default'"
227
+ else
228
+ fail_test "Unknown user access_level should be 'default', got '$ACCESS'"
229
+ fi
230
+
231
+ echo ""
232
+
233
+ # ── Test 10: Ring-fencing — partner sees public + own only ───────────────────
234
+
235
+ echo "Test 10: Ring-fencing — partner sees public + own data only"
236
+ RESULT=$(run_search --user shayan --query "northwind" --backend sqlite --db "$TEST_DB" --format json --max-results 50 2>&1 || true)
237
+
238
+ BAD_SCOPE=$(echo "$RESULT" | python3 -c "
239
+ import sys, json
240
+ d = json.load(sys.stdin)
241
+ allowed = {'public', 'casey-morgan'}
242
+ bad = [m for m in d['matches'] if m.get('user_scope') not in allowed]
243
+ print(len(bad))
244
+ " 2>/dev/null || echo "-1")
245
+
246
+ if [ "$BAD_SCOPE" = "0" ]; then
247
+ pass_test "Partner sees only public + own-scope documents"
248
+ else
249
+ fail_test "Partner should only see public + own scope (saw $BAD_SCOPE violations)"
250
+ fi
251
+
252
+ echo ""
253
+
254
+ # ── Test 11: Fallback — sqlite falls back to grep when DB missing ────────────
255
+
256
+ echo "Test 11: Fallback to grep when SQLite DB missing"
257
+ RESULT=$(python3 "$SEARCH_SCRIPT" --user alex --query "the regulator" --backend sqlite --db /tmp/nonexistent-db-test.db --format json 2>/dev/null || true)
258
+
259
+ BACKEND=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('backend',''))" 2>/dev/null || echo "")
260
+ if [ "$BACKEND" = "grep" ]; then
261
+ pass_test "Fell back to 'grep' backend when DB missing"
262
+ else
263
+ fail_test "Should fall back to 'grep' when DB missing, got '$BACKEND'"
264
+ fi
265
+
266
+ echo ""
267
+
268
+ # ── Test 12: User resolution works with sqlite backend ───────────────────────
269
+
270
+ echo "Test 12: Slack ID resolution works with sqlite backend"
271
+ RESULT=$(run_search --user U099N1JGKRQ --query "test" --backend sqlite --db "$TEST_DB" --format json 2>&1 || true)
272
+
273
+ USER=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['user'])" 2>/dev/null || echo "")
274
+ if [ "$USER" = "alex" ]; then
275
+ pass_test "Slack ID U099N1JGKRQ resolved to 'alex' with sqlite backend"
276
+ else
277
+ fail_test "Slack ID should resolve to 'alex', got '$USER'"
278
+ fi
279
+
280
+ echo ""
281
+
282
+ # ── Test 13: Text format works with sqlite backend ───────────────────────────
283
+
284
+ echo "Test 13: Text format output with sqlite backend"
285
+ RESULT=$(run_search --user alex --query "the regulator" --backend sqlite --db "$TEST_DB" --format text 2>&1 || true)
286
+
287
+ if echo "$RESULT" | grep -q "Access: ceo"; then
288
+ pass_test "Text format includes 'Access: ceo'"
289
+ else
290
+ fail_test "Text format should include 'Access: ceo'"
291
+ fi
292
+
293
+ if echo "$RESULT" | grep -q 'Search: "the regulator"'; then
294
+ pass_test "Text format includes search query"
295
+ else
296
+ fail_test "Text format should include search query"
297
+ fi
298
+
299
+ echo ""
300
+
301
+ # ── Test 14: FTS5 multi-word query ────────────────────────────────────────────
302
+
303
+ echo "Test 14: FTS5 multi-word query"
304
+ RESULT=$(run_search --user alex --query "the regulator compliance" --backend sqlite --db "$TEST_DB" --format json 2>&1 || true)
305
+
306
+ MATCH_COUNT=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result_count'])" 2>/dev/null || echo "0")
307
+ if [ "$MATCH_COUNT" -gt 0 ] 2>/dev/null; then
308
+ pass_test "Multi-word query 'the regulator compliance' returned $MATCH_COUNT results"
309
+ else
310
+ fail_test "Multi-word query should return results"
311
+ fi
312
+
313
+ echo ""
314
+
315
+ # ── Test 15: Incremental indexing skips unchanged files ──────────────────────
316
+
317
+ echo "Test 15: Incremental re-index skips unchanged files"
318
+ OUTPUT=$(run_indexer --db "$TEST_DB" 2>&1 || true)
319
+
320
+ if echo "$OUTPUT" | grep -q "Skipped.*unchanged"; then
321
+ pass_test "Incremental index skipped unchanged files"
322
+ else
323
+ fail_test "Incremental index should report skipped files" "$OUTPUT"
324
+ fi
325
+
326
+ echo ""
327
+
328
+ # ── Test 16: Backward compatibility — grep backend works ────────────────────
329
+
330
+ echo "Test 16: Backward compatibility — grep backend still works"
331
+ RESULT=$(run_search --user alex --query "the regulator" --backend grep --format json 2>&1 || true)
332
+
333
+ BACKEND=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('backend',''))" 2>/dev/null || echo "")
334
+ if [ "$BACKEND" = "grep" ]; then
335
+ pass_test "Grep backend returns backend='grep'"
336
+ else
337
+ fail_test "Grep backend should return backend='grep', got '$BACKEND'"
338
+ fi
339
+
340
+ MATCH_COUNT=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result_count'])" 2>/dev/null || echo "0")
341
+ if [ "$MATCH_COUNT" -gt 0 ] 2>/dev/null; then
342
+ pass_test "Grep backend returned $MATCH_COUNT results"
343
+ else
344
+ fail_test "Grep backend should return results"
345
+ fi
346
+
347
+ echo ""
348
+
349
+ # ── Test 17: Direct SQL scope verification ───────────────────────────────────
350
+
351
+ echo "Test 17: Direct SQL — no scope leakage possible for leadership query"
352
+ LEAKED_SQL=$(python3 -c "
353
+ import sqlite3
354
+ conn = sqlite3.connect('$TEST_DB')
355
+ # Simulate what the search does for leadership: query with scope filter
356
+ allowed = ('public', 'leadership', 'sam-rivera')
357
+ placeholders = ','.join(['?'] * len(allowed))
358
+ sql = f'''SELECT COUNT(*) FROM documents WHERE user_scope NOT IN ({placeholders})'''
359
+ total_private = conn.execute(sql, allowed).fetchone()[0]
360
+ # There should be private docs in the DB that leadership can't see
361
+ total_all = conn.execute('SELECT COUNT(*) FROM documents').fetchone()[0]
362
+ print(f'{total_private},{total_all}')
363
+ conn.close()
364
+ " 2>/dev/null || echo "0,0")
365
+
366
+ PRIVATE=$(echo "$LEAKED_SQL" | cut -d',' -f1)
367
+ TOTAL_DOCS=$(echo "$LEAKED_SQL" | cut -d',' -f2)
368
+ if [ "$PRIVATE" -gt 0 ] 2>/dev/null && [ "$TOTAL_DOCS" -gt "$PRIVATE" ] 2>/dev/null; then
369
+ pass_test "DB has $PRIVATE private docs out of $TOTAL_DOCS total — scope filter would block them"
370
+ else
371
+ fail_test "Expected some private docs that leadership can't see (private=$PRIVATE, total=$TOTAL_DOCS)"
372
+ fi
373
+
374
+ echo ""
375
+
376
+ # ── Test 18: Indexer --stats runs without error ──────────────────────────────
377
+
378
+ echo "Test 18: Indexer --stats runs without error"
379
+ STATS_OUTPUT=$(run_indexer --stats --db "$TEST_DB" 2>&1 || true)
380
+
381
+ if echo "$STATS_OUTPUT" | grep -q "Total documents:"; then
382
+ pass_test "Stats output includes 'Total documents:'"
383
+ else
384
+ fail_test "Stats should include 'Total documents:'" "$STATS_OUTPUT"
385
+ fi
386
+
387
+ echo ""
388
+
389
+ # ── Test 19: Custom --db path works ──────────────────────────────────────────
390
+
391
+ echo "Test 19: Custom --db path works for search"
392
+ CUSTOM_DB="/tmp/test-rag-custom.db"
393
+ rm -f "$CUSTOM_DB"
394
+ run_indexer --full --db "$CUSTOM_DB" >/dev/null 2>&1 || true
395
+
396
+ if [ -f "$CUSTOM_DB" ]; then
397
+ RESULT=$(run_search --user alex --query "test" --backend sqlite --db "$CUSTOM_DB" --format json 2>&1 || true)
398
+ BACKEND=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('backend',''))" 2>/dev/null || echo "")
399
+ if [ "$BACKEND" = "sqlite" ]; then
400
+ pass_test "Custom DB path works for both indexer and search"
401
+ else
402
+ fail_test "Custom DB path search should use sqlite backend"
403
+ fi
404
+ rm -f "$CUSTOM_DB"
405
+ else
406
+ fail_test "Custom DB was not created"
407
+ fi
408
+
409
+ echo ""
410
+
411
+ # ── Test 20: Empty/unusual query handling ────────────────────────────────────
412
+
413
+ echo "Test 20: Unusual query handling"
414
+ # Single character query
415
+ RESULT=$(run_search --user alex --query "x" --backend sqlite --db "$TEST_DB" --format json 2>&1 || true)
416
+ ACCESS=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_level'])" 2>/dev/null || echo "")
417
+ if [ "$ACCESS" = "ceo" ]; then
418
+ pass_test "Single-character query doesn't crash, returns valid JSON"
419
+ else
420
+ fail_test "Single-character query should return valid JSON with access_level"
421
+ fi
422
+
423
+ echo ""
424
+
425
+ # ── Cleanup ───────────────────────────────────────────────────────────────────
426
+
427
+ rm -f "$TEST_DB"
428
+ rm -f /tmp/test-rag-custom.db
429
+
430
+ # ── Summary ───────────────────────────────────────────────────────────────────
431
+
432
+ echo "================================================================"
433
+ echo "Results: $PASS passed, $FAIL failed, $TOTAL total"
434
+ echo "================================================================"
435
+
436
+ if [ "$FAIL" -gt 0 ]; then
437
+ exit 1
438
+ fi
439
+
440
+ echo ""
441
+ echo "All tests passed."
442
+ exit 0
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # test-rag-search.sh — Quick smoke tests for user-context-search.py (Phase 1 RAG)
5
+ #
6
+ # Tests:
7
+ # 1. CEO (alex) can search and gets broad results
8
+ # 2. Leadership (jordan) gets scoped results (no private CEO data)
9
+ # 3. Unknown user gets minimal access (knowledge/sources only)
10
+ # 4. Slack ID resolution works
11
+ # 5. Email resolution works
12
+ #
13
+ # Usage: ./scripts/test-rag-search.sh
14
+
15
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
16
+ SEARCH_SCRIPT="${SCRIPT_DIR}/user-context-search.py"
17
+ PASS=0
18
+ FAIL=0
19
+ TOTAL=0
20
+
21
+ # ── Helpers ────────────────────────────────────────────────────────────────────
22
+
23
+ pass_test() {
24
+ PASS=$((PASS + 1))
25
+ TOTAL=$((TOTAL + 1))
26
+ echo " PASS: $1"
27
+ }
28
+
29
+ fail_test() {
30
+ FAIL=$((FAIL + 1))
31
+ TOTAL=$((TOTAL + 1))
32
+ echo " FAIL: $1"
33
+ if [ -n "${2:-}" ]; then
34
+ echo " Detail: $2"
35
+ fi
36
+ }
37
+
38
+ run_search() {
39
+ python3 "$SEARCH_SCRIPT" "$@" 2>/dev/null
40
+ }
41
+
42
+ # ── Tests ──────────────────────────────────────────────────────────────────────
43
+
44
+ echo "================================================================"
45
+ echo "RAG Phase 1 — user-context-search.py test suite"
46
+ echo "================================================================"
47
+ echo ""
48
+
49
+ # --- Test 1: CEO (alex) search ---
50
+ echo "Test 1: CEO search (alex, query='the regulator')"
51
+ RESULT=$(run_search --user alex --query "the regulator" --format json 2>&1 || true)
52
+
53
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='ceo'" 2>/dev/null; then
54
+ pass_test "access_level is 'ceo'"
55
+ else
56
+ fail_test "access_level should be 'ceo'" "$RESULT"
57
+ fi
58
+
59
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['user']=='alex'" 2>/dev/null; then
60
+ pass_test "user resolved to 'alex'"
61
+ else
62
+ fail_test "user should resolve to 'alex'"
63
+ fi
64
+
65
+ MATCH_COUNT=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result_count'])" 2>/dev/null || echo "0")
66
+ if [ "$MATCH_COUNT" -gt 0 ] 2>/dev/null; then
67
+ pass_test "CEO search returned $MATCH_COUNT results (>0)"
68
+ else
69
+ fail_test "CEO search returned 0 results — expected matches for 'the regulator'"
70
+ fi
71
+
72
+ # Check CEO can search in memory/ and state/ (broad access)
73
+ PATHS_SEARCHED=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(' '.join(d['paths_searched']))" 2>/dev/null || echo "")
74
+ if echo "$PATHS_SEARCHED" | grep -q "memory/"; then
75
+ pass_test "CEO search includes memory/ path"
76
+ else
77
+ fail_test "CEO search should include memory/ path"
78
+ fi
79
+
80
+ if echo "$PATHS_SEARCHED" | grep -q "state/"; then
81
+ pass_test "CEO search includes state/ path"
82
+ else
83
+ fail_test "CEO search should include state/ path"
84
+ fi
85
+
86
+ echo ""
87
+
88
+ # --- Test 2: Leadership (jordan) scoped search ---
89
+ echo "Test 2: Leadership search (jordan, query='northwind')"
90
+ RESULT=$(run_search --user jordan --query "northwind" --format json 2>&1 || true)
91
+
92
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='leadership'" 2>/dev/null; then
93
+ pass_test "access_level is 'leadership'"
94
+ else
95
+ fail_test "access_level should be 'leadership'"
96
+ fi
97
+
98
+ # Leadership should NOT search all of memory/ or state/ or logs/
99
+ if echo "$RESULT" | python3 -c "
100
+ import sys,json
101
+ d=json.load(sys.stdin)
102
+ paths = d['paths_searched']
103
+ # Leadership should not have unrestricted memory/ or state/ or logs/
104
+ bad = [p for p in paths if p in ('memory/', 'state/', 'logs/')]
105
+ assert len(bad) == 0, f'Should not have: {bad}'
106
+ " 2>/dev/null; then
107
+ pass_test "Leadership search does NOT include unrestricted memory/state/logs"
108
+ else
109
+ fail_test "Leadership should not have unrestricted memory/state/logs access"
110
+ fi
111
+
112
+ # Leadership should have knowledge/
113
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'knowledge/' in d['paths_searched']" 2>/dev/null; then
114
+ pass_test "Leadership search includes knowledge/"
115
+ else
116
+ fail_test "Leadership should include knowledge/"
117
+ fi
118
+
119
+ echo ""
120
+
121
+ # --- Test 3: Unknown user (minimal access) ---
122
+ echo "Test 3: Unknown user search (user='nobody', query='northwind')"
123
+ RESULT=$(run_search --user nobody --query "northwind" --format json 2>&1 || true)
124
+
125
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='default'" 2>/dev/null; then
126
+ pass_test "Unknown user gets 'default' access"
127
+ else
128
+ fail_test "Unknown user should get 'default' access"
129
+ fi
130
+
131
+ PATHS_SEARCHED=$(echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(' '.join(d['paths_searched']))" 2>/dev/null || echo "")
132
+ # Default should ONLY search knowledge/sources/
133
+ if echo "$PATHS_SEARCHED" | grep -q "knowledge/sources/"; then
134
+ pass_test "Default access includes knowledge/sources/"
135
+ else
136
+ fail_test "Default access should include knowledge/sources/"
137
+ fi
138
+
139
+ # Should NOT have memory/ or outputs/ or docs/
140
+ if echo "$PATHS_SEARCHED" | grep -qv "memory\|outputs\|docs\|state\|logs"; then
141
+ pass_test "Default access does NOT include private paths"
142
+ else
143
+ fail_test "Default access should not include memory/outputs/docs/state/logs"
144
+ fi
145
+
146
+ echo ""
147
+
148
+ # --- Test 4: Slack ID resolution ---
149
+ echo "Test 4: Slack ID resolution (U099N1JGKRQ -> alex)"
150
+ RESULT=$(run_search --user U099N1JGKRQ --query "test" --format json 2>&1 || true)
151
+
152
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['user']=='alex'" 2>/dev/null; then
153
+ pass_test "Slack ID U099N1JGKRQ resolved to 'alex'"
154
+ else
155
+ fail_test "Slack ID U099N1JGKRQ should resolve to 'alex'"
156
+ fi
157
+
158
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='ceo'" 2>/dev/null; then
159
+ pass_test "Resolved user has 'ceo' access"
160
+ else
161
+ fail_test "Resolved user should have 'ceo' access"
162
+ fi
163
+
164
+ echo ""
165
+
166
+ # --- Test 5: Email resolution ---
167
+ echo "Test 5: Email resolution (nima@example.com -> nima)"
168
+ RESULT=$(run_search --user "nima@example.com" --query "legal" --format json 2>&1 || true)
169
+
170
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['user']=='nima'" 2>/dev/null; then
171
+ pass_test "Email nima@example.com resolved to 'nima'"
172
+ else
173
+ fail_test "Email nima@example.com should resolve to 'nima'"
174
+ fi
175
+
176
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='leadership'" 2>/dev/null; then
177
+ pass_test "Resolved user has 'leadership' access"
178
+ else
179
+ fail_test "Resolved user should have 'leadership' access"
180
+ fi
181
+
182
+ echo ""
183
+
184
+ # --- Test 6: Text format output ---
185
+ echo "Test 6: Text format output"
186
+ RESULT=$(run_search --user alex --query "the regulator" --format text 2>&1 || true)
187
+
188
+ if echo "$RESULT" | grep -q "Access: ceo"; then
189
+ pass_test "Text format includes access level"
190
+ else
191
+ fail_test "Text format should include 'Access: ceo'"
192
+ fi
193
+
194
+ if echo "$RESULT" | grep -q 'Search: "the regulator"'; then
195
+ pass_test "Text format includes query"
196
+ else
197
+ fail_test "Text format should include query"
198
+ fi
199
+
200
+ echo ""
201
+
202
+ # --- Test 7: Partner access (shayan) ---
203
+ echo "Test 7: Partner access (shayan)"
204
+ RESULT=$(run_search --user shayan --query "northwind" --format json 2>&1 || true)
205
+
206
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['access_level']=='partner'" 2>/dev/null; then
207
+ pass_test "Casey gets 'partner' access"
208
+ else
209
+ fail_test "Casey should get 'partner' access"
210
+ fi
211
+
212
+ # Partner should NOT have full knowledge/ or state/
213
+ if echo "$RESULT" | python3 -c "
214
+ import sys,json
215
+ d=json.load(sys.stdin)
216
+ paths = d['paths_searched']
217
+ bad = [p for p in paths if p in ('knowledge/', 'state/', 'memory/', 'logs/')]
218
+ assert len(bad) == 0, f'Should not have: {bad}'
219
+ " 2>/dev/null; then
220
+ pass_test "Partner does NOT have unrestricted knowledge/state/memory/logs"
221
+ else
222
+ fail_test "Partner should not have unrestricted access"
223
+ fi
224
+
225
+ echo ""
226
+
227
+ # --- Test 8: Phone number resolution ---
228
+ echo "Test 8: Phone number resolution (+971585291799 -> alex)"
229
+ RESULT=$(run_search --user "+971585291799" --query "test" --format json 2>&1 || true)
230
+
231
+ if echo "$RESULT" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['user']=='alex'" 2>/dev/null; then
232
+ pass_test "Phone +971585291799 resolved to 'alex'"
233
+ else
234
+ fail_test "Phone +971585291799 should resolve to 'alex'"
235
+ fi
236
+
237
+ echo ""
238
+
239
+ # ── Summary ────────────────────────────────────────────────────────────────────
240
+
241
+ echo "================================================================"
242
+ echo "Results: $PASS passed, $FAIL failed, $TOTAL total"
243
+ echo "================================================================"
244
+
245
+ if [ "$FAIL" -gt 0 ]; then
246
+ exit 1
247
+ fi
248
+
249
+ echo ""
250
+ echo "All tests passed."
251
+ exit 0