@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,659 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ user-context-search.py — Per-user ring-fenced context search (Phase 1 + Phase 2)
4
+
5
+ Searches the agent repo's files scoped to a user's access level.
6
+ Supports two backends:
7
+ - grep (default): file-based grep with user-scoped directory paths (Phase 1)
8
+ - sqlite: FTS5 full-text search over pre-indexed SQLite database (Phase 2)
9
+
10
+ No external dependencies — stdlib only (PyYAML used if available, else manual parsing).
11
+
12
+ Usage:
13
+ python3 scripts/user-context-search.py --user alex --query "the regulator"
14
+ python3 scripts/user-context-search.py --user jordan --query "portal" --max-results 5
15
+ python3 scripts/user-context-search.py --user unknown --query "northwind" --format text
16
+ python3 scripts/user-context-search.py --user alex --query "flight" --backend sqlite
17
+
18
+ Author: Northwind
19
+ Date: 2026-04-03
20
+ """
21
+
22
+ import argparse
23
+ import json
24
+ import os
25
+ import re
26
+ import sqlite3
27
+ import subprocess
28
+ import sys
29
+ from pathlib import Path
30
+
31
+ # ── Constants ──────────────────────────────────────────────────────────────────
32
+
33
+ BASE_DIR = Path(os.environ.get("AGENT_DIR", str(Path(__file__).resolve().parent.parent))) # agent repo root
34
+ CONFIG_PATH = BASE_DIR / "config" / "caller-id-map.yaml"
35
+ DEFAULT_DB_PATH = BASE_DIR / "state" / "rag" / "search.db"
36
+ DEFAULT_MAX_RESULTS = 10
37
+ CONTEXT_LINES = 2 # lines before/after match to include
38
+
39
+
40
+ # ── YAML Parsing (no PyYAML dependency) ────────────────────────────────────────
41
+
42
+ def _try_import_yaml():
43
+ """Try to import PyYAML; return None if unavailable."""
44
+ try:
45
+ import yaml
46
+ return yaml
47
+ except ImportError:
48
+ return None
49
+
50
+
51
+ def _parse_yaml_manually(filepath):
52
+ """
53
+ Minimal YAML parser for the caller-id-map.yaml structure.
54
+ Handles the specific nested structure we expect — not a general-purpose parser.
55
+ Returns a dict matching the YAML structure.
56
+ """
57
+ users = {}
58
+ current_user = None
59
+ current_key = None
60
+
61
+ with open(filepath, "r") as f:
62
+ for raw_line in f:
63
+ line = raw_line.rstrip("\n")
64
+
65
+ # Skip empty lines and comments
66
+ stripped = line.strip()
67
+ if not stripped or stripped.startswith("#"):
68
+ continue
69
+
70
+ # Top-level "users:" key
71
+ if line == "users:":
72
+ continue
73
+
74
+ # User slug line (2-space indent, ends with colon)
75
+ match_user = re.match(r"^ (\w[\w-]*):\s*$", line)
76
+ if match_user:
77
+ current_user = match_user.group(1)
78
+ users[current_user] = {}
79
+ current_key = None
80
+ continue
81
+
82
+ # Key-value pair (4-space indent)
83
+ match_kv = re.match(r'^ (\w[\w_]*):\s*(.*)$', line)
84
+ if match_kv and current_user:
85
+ key = match_kv.group(1)
86
+ value = match_kv.group(2).strip()
87
+
88
+ # Handle list on same line: ["+971..."]
89
+ if value.startswith("[") and value.endswith("]"):
90
+ inner = value[1:-1].strip()
91
+ if inner:
92
+ items = [s.strip().strip('"').strip("'") for s in inner.split(",")]
93
+ users[current_user][key] = items
94
+ else:
95
+ users[current_user][key] = []
96
+ current_key = key
97
+ # Handle quoted string
98
+ elif value.startswith('"') and value.endswith('"'):
99
+ users[current_user][key] = value.strip('"')
100
+ elif value.startswith("'") and value.endswith("'"):
101
+ users[current_user][key] = value.strip("'")
102
+ # Handle empty list start
103
+ elif value == "[]":
104
+ users[current_user][key] = []
105
+ current_key = key
106
+ else:
107
+ users[current_user][key] = value
108
+ current_key = key
109
+ continue
110
+
111
+ # List item (6-space indent, starts with -)
112
+ match_list = re.match(r'^ - (.*)$', line)
113
+ if match_list and current_user and current_key:
114
+ val = match_list.group(1).strip().strip('"').strip("'")
115
+ if not isinstance(users[current_user].get(current_key), list):
116
+ users[current_user][current_key] = []
117
+ users[current_user][current_key].append(val)
118
+ continue
119
+
120
+ return {"users": users}
121
+
122
+
123
+ def load_config(filepath):
124
+ """Load the caller-id-map.yaml config file."""
125
+ if not filepath.exists():
126
+ print(f"Error: Config file not found: {filepath}", file=sys.stderr)
127
+ sys.exit(1)
128
+
129
+ yaml_mod = _try_import_yaml()
130
+ if yaml_mod:
131
+ with open(filepath, "r") as f:
132
+ return yaml_mod.safe_load(f)
133
+ else:
134
+ return _parse_yaml_manually(filepath)
135
+
136
+
137
+ # ── User Resolution ────────────────────────────────────────────────────────────
138
+
139
+ def resolve_user(config, user_id):
140
+ """
141
+ Resolve a user_id to their config entry.
142
+ user_id can be: slug (alex), email (alex@example.com),
143
+ Slack ID (U099N1JGKRQ), or phone (+971585291799).
144
+ Returns (slug, user_dict) or (None, None) if not found.
145
+ """
146
+ users = config.get("users", {})
147
+
148
+ # Direct slug match
149
+ if user_id in users:
150
+ return user_id, users[user_id]
151
+
152
+ # Search by email, slack_id, or phone
153
+ for slug, data in users.items():
154
+ if data.get("email", "") == user_id:
155
+ return slug, data
156
+ if data.get("slack_id", "") == user_id:
157
+ return slug, data
158
+ phones = data.get("phone", [])
159
+ if isinstance(phones, list) and user_id in phones:
160
+ return slug, data
161
+ elif isinstance(phones, str) and phones == user_id:
162
+ return slug, data
163
+
164
+ return None, None
165
+
166
+
167
+ # ── Access Scoping ─────────────────────────────────────────────────────────────
168
+
169
+ # Mapping from user slug to their personal interaction directories
170
+ USER_INTERACTION_DIRS = {
171
+ "alex": {
172
+ "slack_dm": ["memory/interactions/slack/dm-alex-chen/", "memory/interactions/slack/alex-chen/"],
173
+ "email": ["memory/interactions/email/alex-chen/"],
174
+ "phone": ["logs/phone/"],
175
+ },
176
+ "jordan": {
177
+ "slack_dm": [], # No known DM channel yet
178
+ "email": ["memory/interactions/email/sam-rivera/"],
179
+ "phone": [],
180
+ },
181
+ "nima": {
182
+ "slack_dm": [],
183
+ "email": ["memory/interactions/email/jordan-blake/"],
184
+ "phone": [],
185
+ },
186
+ "bronwyn": {
187
+ "slack_dm": [],
188
+ "email": [],
189
+ "phone": [],
190
+ },
191
+ "shayan": {
192
+ "slack_dm": ["memory/interactions/slack/dm-casey-morgan/"],
193
+ "email": [],
194
+ "phone": [],
195
+ },
196
+ }
197
+
198
+ # Shared channel directories accessible to leadership+
199
+ SHARED_CHANNELS = [
200
+ "memory/interactions/slack/ceo-office/",
201
+ "memory/interactions/slack/channels/",
202
+ ]
203
+
204
+
205
+ def get_search_paths(access_level, user_slug):
206
+ """
207
+ Return list of directory paths (relative to BASE_DIR) that this user can search.
208
+
209
+ Access levels:
210
+ ceo — Everything
211
+ leadership — Company knowledge/docs/research/briefs + shared channels + own interactions
212
+ partner — Public knowledge/sources + research + own interactions
213
+ default — Public knowledge/sources only
214
+ """
215
+ paths = []
216
+
217
+ if access_level == "ceo":
218
+ # CEO sees everything
219
+ paths = [
220
+ "memory/",
221
+ "knowledge/",
222
+ "state/",
223
+ "outputs/",
224
+ "docs/",
225
+ "logs/",
226
+ ]
227
+ elif access_level == "leadership":
228
+ # Shared knowledge and docs
229
+ paths = [
230
+ "knowledge/",
231
+ "docs/",
232
+ "outputs/research/",
233
+ "outputs/briefs/",
234
+ "outputs/tasks/",
235
+ ]
236
+ # Shared channels
237
+ for ch in SHARED_CHANNELS:
238
+ paths.append(ch)
239
+ # Own interaction logs
240
+ user_dirs = USER_INTERACTION_DIRS.get(user_slug, {})
241
+ for dir_list in user_dirs.values():
242
+ paths.extend(dir_list)
243
+ elif access_level == "partner":
244
+ # Public knowledge only + own interactions
245
+ paths = [
246
+ "knowledge/sources/",
247
+ "outputs/research/",
248
+ ]
249
+ # Own interaction logs
250
+ user_dirs = USER_INTERACTION_DIRS.get(user_slug, {})
251
+ for dir_list in user_dirs.values():
252
+ paths.extend(dir_list)
253
+ else:
254
+ # Default / unknown — minimal access
255
+ paths = [
256
+ "knowledge/sources/",
257
+ ]
258
+
259
+ return paths
260
+
261
+
262
+ # ── Search Execution ───────────────────────────────────────────────────────────
263
+
264
+ def run_grep_search(query, search_paths, max_results, base_dir):
265
+ """
266
+ Run grep across allowed paths and return matches.
267
+ Uses grep -rli for file discovery, then extracts context from matching files.
268
+ """
269
+ matches = []
270
+ files_found = set()
271
+
272
+ for rel_path in search_paths:
273
+ abs_path = base_dir / rel_path
274
+ if not abs_path.exists():
275
+ continue
276
+
277
+ try:
278
+ # First pass: find matching files (case-insensitive)
279
+ result = subprocess.run(
280
+ ["grep", "-rli", "--include=*.yaml", "--include=*.yml",
281
+ "--include=*.md", "--include=*.jsonl", "--include=*.json",
282
+ "--include=*.txt", "--include=*.html",
283
+ query, str(abs_path)],
284
+ capture_output=True,
285
+ text=True,
286
+ timeout=30,
287
+ )
288
+ if result.returncode == 0:
289
+ for filepath in result.stdout.strip().split("\n"):
290
+ filepath = filepath.strip()
291
+ if filepath and filepath not in files_found:
292
+ files_found.add(filepath)
293
+ except subprocess.TimeoutExpired:
294
+ continue
295
+ except Exception as e:
296
+ print(f"Warning: grep failed on {abs_path}: {e}", file=sys.stderr)
297
+ continue
298
+
299
+ # Second pass: extract context from matching files
300
+ for filepath in sorted(files_found):
301
+ if len(matches) >= max_results:
302
+ break
303
+
304
+ try:
305
+ result = subprocess.run(
306
+ ["grep", "-in", "-C", str(CONTEXT_LINES), query, filepath],
307
+ capture_output=True,
308
+ text=True,
309
+ timeout=15,
310
+ )
311
+ if result.returncode == 0:
312
+ # Parse grep output into individual matches
313
+ context_blocks = result.stdout.strip().split("--\n")
314
+ for block in context_blocks:
315
+ if len(matches) >= max_results:
316
+ break
317
+
318
+ lines = block.strip().split("\n")
319
+ if not lines or not lines[0]:
320
+ continue
321
+
322
+ # Extract line number from first matching line
323
+ line_num = None
324
+ match_line = ""
325
+ context_text = block.strip()
326
+
327
+ for l in lines:
328
+ # grep -n output: "123:matched text" or "123-context text"
329
+ m = re.match(r'^(\d+)[:-](.*)$', l)
330
+ if m:
331
+ if line_num is None:
332
+ line_num = int(m.group(1))
333
+ # The actual match line (with :)
334
+ if ":" in l[:l.index(m.group(2))]:
335
+ match_line = m.group(2).strip()
336
+
337
+ rel_filepath = os.path.relpath(filepath, base_dir)
338
+ matches.append({
339
+ "file": rel_filepath,
340
+ "line": line_num,
341
+ "match": match_line[:500], # truncate long lines
342
+ "context": context_text[:1000], # truncate very long contexts
343
+ })
344
+
345
+ except subprocess.TimeoutExpired:
346
+ continue
347
+ except Exception:
348
+ continue
349
+
350
+ return matches
351
+
352
+
353
+ # ── SQLite FTS5 Search (Phase 2) ──────────────────────────────────────────────
354
+
355
+ def _build_scope_filter(access_level, user_slug):
356
+ """
357
+ Build a list of allowed user_scope values for the given access level.
358
+
359
+ Access level mapping:
360
+ ceo — sees everything (all scopes)
361
+ leadership — "public" + "leadership" + own user slug
362
+ partner — "public" + own user slug
363
+ default — "public" only
364
+ """
365
+ if access_level == "ceo":
366
+ return None # No filter — CEO sees all
367
+ elif access_level == "leadership":
368
+ scopes = ["public", "leadership"]
369
+ if user_slug:
370
+ scopes.append(user_slug)
371
+ return scopes
372
+ elif access_level == "partner":
373
+ scopes = ["public"]
374
+ if user_slug:
375
+ scopes.append(user_slug)
376
+ return scopes
377
+ else:
378
+ return ["public"]
379
+
380
+
381
+ def run_sqlite_search(query, access_level, user_slug, max_results, db_path=None):
382
+ """
383
+ Run FTS5 search against the SQLite index with access control filtering.
384
+ Returns matches in the same format as run_grep_search.
385
+ Falls back to None if database doesn't exist.
386
+ """
387
+ if db_path is None:
388
+ db_path = DEFAULT_DB_PATH
389
+
390
+ if not db_path.exists():
391
+ return None # Signal caller to fall back to grep
392
+
393
+ try:
394
+ conn = sqlite3.connect(str(db_path))
395
+ conn.row_factory = sqlite3.Row
396
+ except Exception:
397
+ return None
398
+
399
+ try:
400
+ # Build the FTS5 query — escape special characters for FTS5 syntax
401
+ # FTS5 uses double quotes for phrase queries; we just do a simple term search
402
+ fts_query = _sanitize_fts_query(query)
403
+
404
+ # Build scope filter
405
+ allowed_scopes = _build_scope_filter(access_level, user_slug)
406
+
407
+ if allowed_scopes is None:
408
+ # CEO — no scope filter
409
+ sql = """
410
+ SELECT d.id, d.source_path, d.source_type, d.user_scope,
411
+ d.content, d.title, d.timestamp, d.metadata_json,
412
+ rank
413
+ FROM documents_fts fts
414
+ JOIN documents d ON d.rowid = fts.rowid
415
+ WHERE documents_fts MATCH ?
416
+ ORDER BY rank
417
+ LIMIT ?
418
+ """
419
+ params = (fts_query, max_results)
420
+ else:
421
+ # Build placeholders for IN clause
422
+ placeholders = ",".join(["?"] * len(allowed_scopes))
423
+ sql = f"""
424
+ SELECT d.id, d.source_path, d.source_type, d.user_scope,
425
+ d.content, d.title, d.timestamp, d.metadata_json,
426
+ rank
427
+ FROM documents_fts fts
428
+ JOIN documents d ON d.rowid = fts.rowid
429
+ WHERE documents_fts MATCH ?
430
+ AND d.user_scope IN ({placeholders})
431
+ ORDER BY rank
432
+ LIMIT ?
433
+ """
434
+ params = (fts_query, *allowed_scopes, max_results)
435
+
436
+ rows = conn.execute(sql, params).fetchall()
437
+
438
+ matches = []
439
+ for row in rows:
440
+ content = row["content"] or ""
441
+ # Extract a snippet around the match (first occurrence)
442
+ match_line = _extract_snippet(content, query)
443
+ context_text = _extract_context(content, query)
444
+
445
+ matches.append({
446
+ "file": row["source_path"],
447
+ "line": None,
448
+ "match": match_line[:500],
449
+ "context": context_text[:1000],
450
+ "score": abs(row["rank"]) if row["rank"] else 0,
451
+ "source_type": row["source_type"],
452
+ "user_scope": row["user_scope"],
453
+ "timestamp": row["timestamp"] or "",
454
+ })
455
+
456
+ conn.close()
457
+ return matches
458
+
459
+ except Exception as e:
460
+ print(f"Warning: SQLite search failed: {e}", file=sys.stderr)
461
+ conn.close()
462
+ return None
463
+
464
+
465
+ def _sanitize_fts_query(query):
466
+ """
467
+ Sanitize a query string for FTS5 syntax.
468
+ Wraps each word in quotes to handle special characters.
469
+ """
470
+ # Remove FTS5 special operators that could cause syntax errors
471
+ # Keep it simple: split into words and quote each one
472
+ words = query.strip().split()
473
+ if not words:
474
+ return '""'
475
+
476
+ # If it's a single word, just return it (FTS5 handles simple terms fine)
477
+ # For multi-word: join with spaces (implicit AND in FTS5)
478
+ sanitized = []
479
+ for word in words:
480
+ # Remove characters that break FTS5 syntax
481
+ clean = re.sub(r'[^\w\-@.]', '', word)
482
+ if clean:
483
+ sanitized.append(f'"{clean}"')
484
+
485
+ return " ".join(sanitized) if sanitized else '""'
486
+
487
+
488
+ def _extract_snippet(content, query, max_len=200):
489
+ """Extract the line containing the first match of query in content."""
490
+ query_lower = query.lower()
491
+ content_lower = content.lower()
492
+ idx = content_lower.find(query_lower)
493
+ if idx == -1:
494
+ # Try individual words
495
+ for word in query.split():
496
+ idx = content_lower.find(word.lower())
497
+ if idx != -1:
498
+ break
499
+ if idx == -1:
500
+ return content[:max_len]
501
+
502
+ # Find the line containing the match
503
+ start = content.rfind("\n", 0, idx)
504
+ start = start + 1 if start != -1 else 0
505
+ end = content.find("\n", idx)
506
+ end = end if end != -1 else len(content)
507
+
508
+ line = content[start:end].strip()
509
+ return line[:max_len]
510
+
511
+
512
+ def _extract_context(content, query, context_chars=400):
513
+ """Extract a window of context around the match."""
514
+ query_lower = query.lower()
515
+ content_lower = content.lower()
516
+ idx = content_lower.find(query_lower)
517
+ if idx == -1:
518
+ for word in query.split():
519
+ idx = content_lower.find(word.lower())
520
+ if idx != -1:
521
+ break
522
+ if idx == -1:
523
+ return content[:context_chars]
524
+
525
+ start = max(0, idx - context_chars // 2)
526
+ end = min(len(content), idx + context_chars // 2)
527
+ return content[start:end].strip()
528
+
529
+
530
+ # ── Output Formatting ──────────────────────────────────────────────────────────
531
+
532
+ def format_json(user_slug, user_data, access_level, paths_searched, matches, query):
533
+ """Format results as JSON."""
534
+ output = {
535
+ "query": query,
536
+ "user": user_slug or "unknown",
537
+ "user_name": user_data.get("name", "Unknown") if user_data else "Unknown",
538
+ "access_level": access_level,
539
+ "paths_searched": paths_searched,
540
+ "result_count": len(matches),
541
+ "matches": matches,
542
+ }
543
+ return json.dumps(output, indent=2)
544
+
545
+
546
+ def format_text(user_slug, user_data, access_level, paths_searched, matches, query):
547
+ """Format results as human-readable text."""
548
+ lines = []
549
+ name = user_data.get("name", "Unknown") if user_data else "Unknown"
550
+ lines.append(f"Search: \"{query}\"")
551
+ lines.append(f"User: {name} ({user_slug or 'unknown'}) | Access: {access_level}")
552
+ lines.append(f"Paths searched: {len(paths_searched)}")
553
+ lines.append(f"Results: {len(matches)}")
554
+ lines.append("-" * 60)
555
+
556
+ if not matches:
557
+ lines.append("No matches found.")
558
+ else:
559
+ for i, m in enumerate(matches, 1):
560
+ lines.append(f"\n[{i}] {m['file']}:{m.get('line', '?')}")
561
+ lines.append(f" {m['match']}")
562
+
563
+ return "\n".join(lines)
564
+
565
+
566
+ # ── Main ───────────────────────────────────────────────────────────────────────
567
+
568
+ def main():
569
+ parser = argparse.ArgumentParser(
570
+ description="Per-user ring-fenced context search (Phase 1 + Phase 2). "
571
+ "Searches the agent repo's files scoped to a user's access level.",
572
+ epilog="Examples:\n"
573
+ " %(prog)s --user alex --query \"the regulator\"\n"
574
+ " %(prog)s --user jordan --query \"portal\" --max-results 5\n"
575
+ " %(prog)s --user U099N1JGKRQ --query \"flight\" --format text\n"
576
+ " %(prog)s --user unknown --query \"northwind\"\n"
577
+ " %(prog)s --user alex --query \"flight\" --backend sqlite\n",
578
+ formatter_class=argparse.RawDescriptionHelpFormatter,
579
+ )
580
+ parser.add_argument(
581
+ "--user", required=True,
582
+ help="User identifier: slug (alex), email (alex@example.com), "
583
+ "Slack ID (U099N1JGKRQ), or phone (+971585291799)"
584
+ )
585
+ parser.add_argument(
586
+ "--query", required=True,
587
+ help="Search term (case-insensitive grep pattern)"
588
+ )
589
+ parser.add_argument(
590
+ "--max-results", type=int, default=DEFAULT_MAX_RESULTS,
591
+ help=f"Maximum number of results to return (default: {DEFAULT_MAX_RESULTS})"
592
+ )
593
+ parser.add_argument(
594
+ "--format", choices=["json", "text"], default="json",
595
+ help="Output format: json (default, machine-readable) or text (human-readable)"
596
+ )
597
+ parser.add_argument(
598
+ "--backend", choices=["grep", "sqlite"], default="grep",
599
+ help="Search backend: grep (default, Phase 1) or sqlite (Phase 2 FTS5)"
600
+ )
601
+ parser.add_argument(
602
+ "--db", type=str, default=None,
603
+ help="Path to SQLite database (only used with --backend sqlite)"
604
+ )
605
+
606
+ args = parser.parse_args()
607
+
608
+ # Load config
609
+ config = load_config(CONFIG_PATH)
610
+
611
+ # Resolve user
612
+ user_slug, user_data = resolve_user(config, args.user)
613
+
614
+ if user_slug:
615
+ access_level = user_data.get("access_level", "default")
616
+ else:
617
+ # Unknown user — default (minimal) access
618
+ user_slug = None
619
+ user_data = None
620
+ access_level = "default"
621
+
622
+ # Determine backend and run search
623
+ backend_used = args.backend
624
+ matches = None
625
+
626
+ if args.backend == "sqlite":
627
+ db_path = Path(args.db) if args.db else DEFAULT_DB_PATH
628
+ matches = run_sqlite_search(
629
+ args.query, access_level, user_slug,
630
+ args.max_results, db_path
631
+ )
632
+ if matches is None:
633
+ # Fall back to grep if SQLite DB doesn't exist
634
+ print("Warning: SQLite database not found, falling back to grep backend",
635
+ file=sys.stderr)
636
+ backend_used = "grep"
637
+
638
+ if matches is None:
639
+ # Use grep backend (default or fallback)
640
+ search_paths = get_search_paths(access_level, user_slug)
641
+ valid_paths = [p for p in search_paths if (BASE_DIR / p).exists()]
642
+ matches = run_grep_search(args.query, valid_paths, args.max_results, BASE_DIR)
643
+ else:
644
+ # For sqlite backend, build valid_paths for the output
645
+ valid_paths = ["(sqlite index)"]
646
+
647
+ # Output
648
+ if args.format == "json":
649
+ output = format_json(user_slug, user_data, access_level, valid_paths, matches, args.query)
650
+ # Add backend info to JSON output
651
+ output_dict = json.loads(output)
652
+ output_dict["backend"] = backend_used
653
+ print(json.dumps(output_dict, indent=2))
654
+ else:
655
+ print(format_text(user_slug, user_data, access_level, valid_paths, matches, args.query))
656
+
657
+
658
+ if __name__ == "__main__":
659
+ main()