@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,95 @@
1
+ #!/bin/bash
2
+ # launchd-socket-mode-wrapper.sh — Bootstraps env for slack-cloud-relay-client.mjs
3
+ # under launchd.
4
+ #
5
+ # Mirrors launchd-wrapper.sh exactly: launchd's bare env doesn't include
6
+ # HOME, PATH, or AGENT_ROOT, so we hydrate them before exec'ing the
7
+ # Socket Mode listener. Logs land on the external SSD when available
8
+ # (same fallback semantics as the main daemon wrapper).
9
+ #
10
+ # This wrapper is exec'd by ai.maestro.{firstname}-slack-cloud-relay.plist.
11
+
12
+ set -e
13
+
14
+ AGENT_ROOT="$(cd "$(dirname "$0")/../.." && pwd -P)"
15
+ export AGENT_ROOT
16
+ export HOME="${HOME:-/Users/$(whoami)}"
17
+ export USER="${USER:-$(whoami)}"
18
+ export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
19
+
20
+ # ── SSD redirect ────────────────────────────────────────────────────────────
21
+ # If an external SSD is mounted at /Volumes/{name}, redirect:
22
+ # - Claude Code per-cwd temp (CLAUDE_CODE_TMPDIR)
23
+ # - Listener stdout/stderr (via shell redirection at exec time)
24
+ #
25
+ # Detection mirrors launchd-wrapper.sh — first volume under /Volumes that's
26
+ # not a system mount; MAESTRO_SSD_VOLUME env var overrides if multiple SSDs.
27
+
28
+ SSD_VOLUME="${MAESTRO_SSD_VOLUME:-}"
29
+ if [ -z "$SSD_VOLUME" ]; then
30
+ for v in /Volumes/*-SSD /Volumes/*SSD* /Volumes/maestro-data; do
31
+ if [ -d "$v" ] && [ "$v" != "/Volumes/Macintosh HD" ]; then
32
+ SSD_VOLUME="$v"
33
+ break
34
+ fi
35
+ done
36
+ fi
37
+
38
+ AGENT_NAME="$(basename "$AGENT_ROOT" | sed 's/-ai$//')"
39
+ SSD_AGENT_ROOT=""
40
+ SSD_WRITABLE=0
41
+ if [ -n "$SSD_VOLUME" ] && [ -d "$SSD_VOLUME" ]; then
42
+ SSD_AGENT_ROOT="$SSD_VOLUME/maestro/$AGENT_NAME"
43
+ if mkdir -p "$SSD_AGENT_ROOT/claude-tmp" "$SSD_AGENT_ROOT/logs/slack-cloud-relay" 2>/dev/null && \
44
+ touch "$SSD_AGENT_ROOT/.write-test-$$" 2>/dev/null; then
45
+ rm -f "$SSD_AGENT_ROOT/.write-test-$$"
46
+ SSD_WRITABLE=1
47
+ export CLAUDE_CODE_TMPDIR="$SSD_AGENT_ROOT/claude-tmp"
48
+ fi
49
+ fi
50
+
51
+ cd "$AGENT_ROOT"
52
+
53
+ # Resolve node binary — prefer nvm, fall back to homebrew, then system.
54
+ NODE_BIN=""
55
+ for candidate in \
56
+ "$HOME/.nvm/versions/node/v24.11.1/bin/node" \
57
+ "$HOME/.nvm/versions/node/v24/bin/node" \
58
+ "$HOME/.nvm/versions/node/v22/bin/node" \
59
+ "$HOME/.nvm/versions/node/v20/bin/node" \
60
+ /opt/homebrew/bin/node \
61
+ /usr/local/bin/node \
62
+ /usr/bin/node; do
63
+ if [ -x "$candidate" ]; then
64
+ NODE_BIN="$candidate"
65
+ break
66
+ fi
67
+ done
68
+ if [ -z "$NODE_BIN" ] && [ -d "$HOME/.nvm/versions/node" ]; then
69
+ NODE_BIN=$(ls -1d "$HOME/.nvm/versions/node"/v*/bin/node 2>/dev/null | sort -V | tail -1)
70
+ fi
71
+ if [ -z "$NODE_BIN" ] || [ ! -x "$NODE_BIN" ]; then
72
+ echo "[slack-cloud-relay-wrapper] FATAL: could not find node binary" >&2
73
+ exit 127
74
+ fi
75
+
76
+ # Node 22.4+ is required for the global WebSocket. Warn (don't fail) on
77
+ # older versions — the user might have polyfilled via `--experimental-websocket`
78
+ # or installed the `ws` package as a fallback.
79
+ NODE_VERSION="$("$NODE_BIN" --version 2>/dev/null || echo 'v0.0.0')"
80
+ NODE_MAJOR="$(echo "$NODE_VERSION" | sed -E 's/^v([0-9]+).*/\1/')"
81
+ if [ "$NODE_MAJOR" -lt 22 ] 2>/dev/null; then
82
+ echo "[slack-cloud-relay-wrapper] WARNING: Node $NODE_VERSION is older than v22 — global WebSocket may be missing." >&2
83
+ fi
84
+
85
+ # Exec the listener. Prefer SSD log path if writable, otherwise fall back
86
+ # to internal disk so the listener stays up even when macOS denies launchd
87
+ # write access to /Volumes/{name}.
88
+ if [ "$SSD_WRITABLE" = "1" ]; then
89
+ LISTENER_LOG="$SSD_AGENT_ROOT/logs/slack-cloud-relay/listener-$(date +%Y-%m-%d).log"
90
+ exec "$NODE_BIN" "$AGENT_ROOT/scripts/poller/slack-cloud-relay-client.mjs" >> "$LISTENER_LOG" 2>&1
91
+ else
92
+ LISTENER_LOG="$AGENT_ROOT/logs/polling/slack-cloud-relay-$(date +%Y-%m-%d).log"
93
+ mkdir -p "$(dirname "$LISTENER_LOG")" 2>/dev/null || true
94
+ exec "$NODE_BIN" "$AGENT_ROOT/scripts/poller/slack-cloud-relay-client.mjs" >> "$LISTENER_LOG" 2>&1
95
+ fi
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ # launchd-socket-mode-wrapper.sh — Bootstraps env for slack-socket-mode.mjs
3
+ # under launchd.
4
+ #
5
+ # Mirrors launchd-wrapper.sh exactly: launchd's bare env doesn't include
6
+ # HOME, PATH, or AGENT_ROOT, so we hydrate them before exec'ing the
7
+ # Socket Mode listener. Logs land on the external SSD when available
8
+ # (same fallback semantics as the main daemon wrapper).
9
+ #
10
+ # This wrapper is exec'd by ai.maestro.{firstname}-slack-socket.plist.
11
+
12
+ set -e
13
+
14
+ AGENT_ROOT="$(cd "$(dirname "$0")/../.." && pwd -P)"
15
+ export AGENT_ROOT
16
+ export HOME="${HOME:-/Users/$(whoami)}"
17
+ export USER="${USER:-$(whoami)}"
18
+ export PATH="/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$PATH"
19
+
20
+ # ── SSD redirect ────────────────────────────────────────────────────────────
21
+ # If an external SSD is mounted at /Volumes/{name}, redirect:
22
+ # - Claude Code per-cwd temp (CLAUDE_CODE_TMPDIR)
23
+ # - Listener stdout/stderr (via shell redirection at exec time)
24
+ #
25
+ # Detection mirrors launchd-wrapper.sh — first volume under /Volumes that's
26
+ # not a system mount; MAESTRO_SSD_VOLUME env var overrides if multiple SSDs.
27
+
28
+ SSD_VOLUME="${MAESTRO_SSD_VOLUME:-}"
29
+ if [ -z "$SSD_VOLUME" ]; then
30
+ for v in /Volumes/*-SSD /Volumes/*SSD* /Volumes/maestro-data; do
31
+ if [ -d "$v" ] && [ "$v" != "/Volumes/Macintosh HD" ]; then
32
+ SSD_VOLUME="$v"
33
+ break
34
+ fi
35
+ done
36
+ fi
37
+
38
+ AGENT_NAME="$(basename "$AGENT_ROOT" | sed 's/-ai$//')"
39
+ SSD_AGENT_ROOT=""
40
+ SSD_WRITABLE=0
41
+ if [ -n "$SSD_VOLUME" ] && [ -d "$SSD_VOLUME" ]; then
42
+ SSD_AGENT_ROOT="$SSD_VOLUME/maestro/$AGENT_NAME"
43
+ if mkdir -p "$SSD_AGENT_ROOT/claude-tmp" "$SSD_AGENT_ROOT/logs/slack-socket" 2>/dev/null && \
44
+ touch "$SSD_AGENT_ROOT/.write-test-$$" 2>/dev/null; then
45
+ rm -f "$SSD_AGENT_ROOT/.write-test-$$"
46
+ SSD_WRITABLE=1
47
+ export CLAUDE_CODE_TMPDIR="$SSD_AGENT_ROOT/claude-tmp"
48
+ fi
49
+ fi
50
+
51
+ cd "$AGENT_ROOT"
52
+
53
+ # Resolve node binary — prefer nvm, fall back to homebrew, then system.
54
+ NODE_BIN=""
55
+ for candidate in \
56
+ "$HOME/.nvm/versions/node/v24.11.1/bin/node" \
57
+ "$HOME/.nvm/versions/node/v24/bin/node" \
58
+ "$HOME/.nvm/versions/node/v22/bin/node" \
59
+ "$HOME/.nvm/versions/node/v20/bin/node" \
60
+ /opt/homebrew/bin/node \
61
+ /usr/local/bin/node \
62
+ /usr/bin/node; do
63
+ if [ -x "$candidate" ]; then
64
+ NODE_BIN="$candidate"
65
+ break
66
+ fi
67
+ done
68
+ if [ -z "$NODE_BIN" ] && [ -d "$HOME/.nvm/versions/node" ]; then
69
+ NODE_BIN=$(ls -1d "$HOME/.nvm/versions/node"/v*/bin/node 2>/dev/null | sort -V | tail -1)
70
+ fi
71
+ if [ -z "$NODE_BIN" ] || [ ! -x "$NODE_BIN" ]; then
72
+ echo "[slack-socket-wrapper] FATAL: could not find node binary" >&2
73
+ exit 127
74
+ fi
75
+
76
+ # Node 22.4+ is required for the global WebSocket. Warn (don't fail) on
77
+ # older versions — the user might have polyfilled via `--experimental-websocket`
78
+ # or installed the `ws` package as a fallback.
79
+ NODE_VERSION="$("$NODE_BIN" --version 2>/dev/null || echo 'v0.0.0')"
80
+ NODE_MAJOR="$(echo "$NODE_VERSION" | sed -E 's/^v([0-9]+).*/\1/')"
81
+ if [ "$NODE_MAJOR" -lt 22 ] 2>/dev/null; then
82
+ echo "[slack-socket-wrapper] WARNING: Node $NODE_VERSION is older than v22 — global WebSocket may be missing." >&2
83
+ fi
84
+
85
+ # Exec the listener. Prefer SSD log path if writable, otherwise fall back
86
+ # to internal disk so the listener stays up even when macOS denies launchd
87
+ # write access to /Volumes/{name}.
88
+ if [ "$SSD_WRITABLE" = "1" ]; then
89
+ LISTENER_LOG="$SSD_AGENT_ROOT/logs/slack-socket/listener-$(date +%Y-%m-%d).log"
90
+ exec "$NODE_BIN" "$AGENT_ROOT/scripts/poller/slack-socket-mode.mjs" >> "$LISTENER_LOG" 2>&1
91
+ else
92
+ LISTENER_LOG="$AGENT_ROOT/logs/polling/slack-socket-$(date +%Y-%m-%d).log"
93
+ mkdir -p "$(dirname "$LISTENER_LOG")" 2>/dev/null || true
94
+ exec "$NODE_BIN" "$AGENT_ROOT/scripts/poller/slack-socket-mode.mjs" >> "$LISTENER_LOG" 2>&1
95
+ fi
@@ -0,0 +1,493 @@
1
+ /**
2
+ * check-docs-accuracy.mjs — docs-accuracy CI guard (gaps-product-quality P1-3).
3
+ *
4
+ * Docs drift ships in every created repo: README/CLAUDE.md claim agent/tool
5
+ * counts that no longer match reality, `teams/`+`workflows/` reference agents
6
+ * that were deleted, and README relative links rot. For an auditor this is
7
+ * load-bearing documentation fiction. This guard makes a subset of docs
8
+ * accuracy a CI-enforced property using only Node builtins.
9
+ *
10
+ * What it asserts
11
+ * --------------
12
+ * 1. COUNTS — the agent-definition and tool counts asserted in README.md /
13
+ * CLAUDE.md prose match reality (count `agents/<id>/` dirs; count tools via
14
+ * `getAllToolsByCategory()` in lib/tool-definitions.js).
15
+ * 2. DANGLING AGENT REFS — every `members:` entry in `teams/*.yaml` and every
16
+ * `agent:` step in `workflows/*.yaml` resolves to an `agents/<id>/` dir.
17
+ * 3. BROKEN LINKS — every relative link in README.md resolves to a real file.
18
+ *
19
+ * Severity policy (fail-open per the audit, and scoped to this lane's edits)
20
+ * ------------------------------------------------------------------------
21
+ * `run()` returns non-zero ONLY for broken README links — the one invariant
22
+ * that is clean in the tree today and so can be ratcheted shut without churn.
23
+ * Count drift and dangling agent refs are surfaced as WARN, not FAIL, because:
24
+ * - the count claims live in README.md/CLAUDE.md, pre-scrub prose this guard
25
+ * can flag but the count fix is a separate docs edit;
26
+ * - workflow `agent:` refs are dispatched via an LLM-mediated trigger prompt
27
+ * (not a hard symbol resolution), so a missing dir is a smell, not a build
28
+ * break — the spec calls for WARN here explicitly;
29
+ * - team `members:` drift is the known "21 deleted agents" backlog item,
30
+ * tracked for tokenisation rather than a regression this guard introduces.
31
+ * The pure analyzers below return ALL drift regardless of severity, so tests
32
+ * (and a future strict mode) can assert detection of each class directly.
33
+ *
34
+ * Pure, dependency-light: Node builtins only. ESM (maestro is "type":"module").
35
+ *
36
+ * Usage: `node scripts/ci/check-docs-accuracy.mjs` (exit 0 ok / 1 broken link)
37
+ * @module scripts/ci/check-docs-accuracy
38
+ */
39
+
40
+ "use strict";
41
+
42
+ import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
43
+ import path from "node:path";
44
+ import { fileURLToPath } from "node:url";
45
+
46
+ /** Repo root: two levels up from scripts/ci/. @type {string} */
47
+ export const REPO_ROOT = path.resolve(
48
+ fileURLToPath(new URL(".", import.meta.url)),
49
+ "..",
50
+ "..",
51
+ );
52
+
53
+ // ── Reality: count what actually exists ─────────────────────────────────────
54
+
55
+ /**
56
+ * Count agent definitions = immediate subdirectories of `agents/` (each holds an
57
+ * `agent.md`). Returns 0 if the dir is absent.
58
+ *
59
+ * @param {string} [cwd=REPO_ROOT]
60
+ * @returns {number}
61
+ */
62
+ export function countAgentDirs(cwd = REPO_ROOT) {
63
+ const dir = path.join(cwd, "agents");
64
+ if (!existsSync(dir)) return 0;
65
+ let n = 0;
66
+ for (const name of readdirSync(dir)) {
67
+ if (name.startsWith(".")) continue;
68
+ let st;
69
+ try {
70
+ st = statSync(path.join(dir, name));
71
+ } catch {
72
+ continue;
73
+ }
74
+ if (st.isDirectory()) n++;
75
+ }
76
+ return n;
77
+ }
78
+
79
+ /**
80
+ * The set of agent ids (dir names) under `agents/`. Used to resolve refs.
81
+ *
82
+ * @param {string} [cwd=REPO_ROOT]
83
+ * @returns {Set<string>}
84
+ */
85
+ export function agentIdSet(cwd = REPO_ROOT) {
86
+ const dir = path.join(cwd, "agents");
87
+ const out = new Set();
88
+ if (!existsSync(dir)) return out;
89
+ for (const name of readdirSync(dir)) {
90
+ if (name.startsWith(".")) continue;
91
+ let st;
92
+ try {
93
+ st = statSync(path.join(dir, name));
94
+ } catch {
95
+ continue;
96
+ }
97
+ if (st.isDirectory()) out.add(name);
98
+ }
99
+ return out;
100
+ }
101
+
102
+ /**
103
+ * Count tool definitions by importing the canonical category map. Falls back to
104
+ * 0 (and a reason) if the module cannot be loaded, so the guard never crashes a
105
+ * partial checkout.
106
+ *
107
+ * @param {string} [cwd=REPO_ROOT]
108
+ * @returns {Promise<number>}
109
+ */
110
+ export async function countTools(cwd = REPO_ROOT) {
111
+ const modPath = path.join(cwd, "lib", "tool-definitions.js");
112
+ if (!existsSync(modPath)) return 0;
113
+ const mod = await import(pathToFileUrl(modPath));
114
+ if (typeof mod.getAllToolsByCategory !== "function") return 0;
115
+ const cats = mod.getAllToolsByCategory();
116
+ let total = 0;
117
+ for (const k of Object.keys(cats)) {
118
+ if (Array.isArray(cats[k])) total += cats[k].length;
119
+ }
120
+ return total;
121
+ }
122
+
123
+ /** Build a file:// URL for a dynamic import (avoids Windows-path import bugs). */
124
+ function pathToFileUrl(p) {
125
+ return new URL(`file://${path.resolve(p)}`).href;
126
+ }
127
+
128
+ // ── Claims: extract the numbers the docs assert ─────────────────────────────
129
+
130
+ /**
131
+ * Extract claimed agent counts from a doc body. Matches phrasings like
132
+ * "34 agent definitions", "34 sub-agent definitions", "34 specialist agent
133
+ * definitions". Deliberately narrow so unrelated numbers (ports, versions, the
134
+ * 40–60 generated sub-agents range) are not misread as the static count.
135
+ *
136
+ * @param {string} text
137
+ * @returns {number[]} every distinct claimed count, in document order
138
+ */
139
+ export function extractAgentCountClaims(text) {
140
+ if (typeof text !== "string") return [];
141
+ const out = [];
142
+ // "<n> [specialist|sub-]agent definitions" — the static-roster phrasing.
143
+ const re = /\b(\d{1,4})\s+(?:[a-z-]+\s+){0,2}(?:sub-)?agent\s+definitions\b/gi;
144
+ let m;
145
+ while ((m = re.exec(text))) out.push(Number(m[1]));
146
+ return out;
147
+ }
148
+
149
+ /**
150
+ * Extract claimed tool counts from a doc body. Matches "30 tools across",
151
+ * "30 tools." style total claims; ignores the per-access-level counts
152
+ * ("29 tools", "(24 tools)") which are a separate, intentionally-different
153
+ * number.
154
+ *
155
+ * @param {string} text
156
+ * @returns {number[]}
157
+ */
158
+ export function extractToolCountClaims(text) {
159
+ if (typeof text !== "string") return [];
160
+ const out = [];
161
+ const re = /\b(\d{1,4})\s+tools\s+across\b/gi;
162
+ let m;
163
+ while ((m = re.exec(text))) out.push(Number(m[1]));
164
+ return out;
165
+ }
166
+
167
+ // ── Dangling agent references in teams/ and workflows/ ──────────────────────
168
+
169
+ /**
170
+ * Parse the `members:` block of a team YAML into a list of agent ids. A tiny
171
+ * structural line-walker (no js-yaml dependency): collects `- <id>` items that
172
+ * are indented deeper than the `members:` key, stopping at the next key at or
173
+ * above the members indent. This avoids misreading sibling list blocks
174
+ * (`responsibilities:`, `principles:`) as agent refs.
175
+ *
176
+ * @param {string} yamlText
177
+ * @returns {string[]}
178
+ */
179
+ export function parseTeamMembers(yamlText) {
180
+ if (typeof yamlText !== "string") return [];
181
+ const lines = yamlText.split(/\r?\n/);
182
+ const out = [];
183
+ let inMembers = false;
184
+ let keyIndent = 0;
185
+ for (const line of lines) {
186
+ if (!inMembers) {
187
+ const k = line.match(/^(\s*)members:\s*(#.*)?$/);
188
+ if (k) {
189
+ inMembers = true;
190
+ keyIndent = k[1].length;
191
+ }
192
+ continue;
193
+ }
194
+ if (line.trim() === "" || line.trim().startsWith("#")) continue;
195
+ const item = line.match(/^(\s*)-\s*([A-Za-z0-9][A-Za-z0-9_-]*)\s*(#.*)?$/);
196
+ if (item && item[1].length > keyIndent) {
197
+ out.push(item[2]);
198
+ continue;
199
+ }
200
+ // Any non-deeper line (a sibling/parent key, or a non-id list item) ends
201
+ // the members block.
202
+ inMembers = false;
203
+ }
204
+ return out;
205
+ }
206
+
207
+ /**
208
+ * Parse every `agent: <id>` step value from a workflow YAML. These are the
209
+ * agents a workflow dispatches to. `agent-self` is a sentinel (the executing
210
+ * agent), not a roster id, and is excluded.
211
+ *
212
+ * @param {string} yamlText
213
+ * @returns {string[]}
214
+ */
215
+ export function parseWorkflowAgentRefs(yamlText) {
216
+ if (typeof yamlText !== "string") return [];
217
+ const out = [];
218
+ const re = /^\s*-?\s*agent:\s*([A-Za-z0-9][A-Za-z0-9_-]*)\s*(#.*)?$/gm;
219
+ let m;
220
+ while ((m = re.exec(yamlText))) {
221
+ if (m[1] === "agent-self") continue;
222
+ out.push(m[1]);
223
+ }
224
+ return out;
225
+ }
226
+
227
+ /** Recursively list files matching a suffix under a dir. @returns {string[]} */
228
+ function listYaml(dir) {
229
+ const out = [];
230
+ if (!existsSync(dir)) return out;
231
+ for (const name of readdirSync(dir)) {
232
+ if (name.startsWith(".") || name === "node_modules") continue;
233
+ const p = path.join(dir, name);
234
+ let st;
235
+ try {
236
+ st = statSync(p);
237
+ } catch {
238
+ continue;
239
+ }
240
+ if (st.isDirectory()) out.push(...listYaml(p));
241
+ else if (name.endsWith(".yaml") || name.endsWith(".yml")) out.push(p);
242
+ }
243
+ return out;
244
+ }
245
+
246
+ /**
247
+ * Find dangling agent references across teams/ and workflows/.
248
+ *
249
+ * @param {object} [opts]
250
+ * @param {string} [opts.cwd=REPO_ROOT]
251
+ * @param {Set<string>} [opts.ids] - pre-computed agent id set (else read disk)
252
+ * @returns {{ teamRefs: Array<{file:string, agent:string}>, workflowRefs: Array<{file:string, agent:string}> }}
253
+ */
254
+ export function findDanglingAgentRefs(opts = {}) {
255
+ const cwd = opts.cwd || REPO_ROOT;
256
+ const ids = opts.ids || agentIdSet(cwd);
257
+ const teamRefs = [];
258
+ const workflowRefs = [];
259
+
260
+ // De-dupe per (file, agent): a workflow can dispatch to the same agent across
261
+ // many steps, and a team could list it twice — one drift row per pair is the
262
+ // useful unit.
263
+ const seen = new Set();
264
+
265
+ for (const file of listYaml(path.join(cwd, "teams"))) {
266
+ let body;
267
+ try {
268
+ body = readFileSync(file, "utf8");
269
+ } catch {
270
+ continue;
271
+ }
272
+ const rel = path.relative(cwd, file);
273
+ for (const a of parseTeamMembers(body)) {
274
+ if (ids.has(a)) continue;
275
+ const key = `t:${rel}:${a}`;
276
+ if (seen.has(key)) continue;
277
+ seen.add(key);
278
+ teamRefs.push({ file: rel, agent: a });
279
+ }
280
+ }
281
+
282
+ for (const file of listYaml(path.join(cwd, "workflows"))) {
283
+ let body;
284
+ try {
285
+ body = readFileSync(file, "utf8");
286
+ } catch {
287
+ continue;
288
+ }
289
+ const rel = path.relative(cwd, file);
290
+ for (const a of parseWorkflowAgentRefs(body)) {
291
+ if (ids.has(a)) continue;
292
+ const key = `w:${rel}:${a}`;
293
+ if (seen.has(key)) continue;
294
+ seen.add(key);
295
+ workflowRefs.push({ file: rel, agent: a });
296
+ }
297
+ }
298
+
299
+ return { teamRefs, workflowRefs };
300
+ }
301
+
302
+ // ── README relative-link resolution ─────────────────────────────────────────
303
+
304
+ /**
305
+ * Extract relative (on-disk) link targets from markdown. External (http(s)/
306
+ * mailto/tel/data), pure-anchor (#…) links are skipped; anchors and query
307
+ * strings are stripped from the path before resolution.
308
+ *
309
+ * @param {string} md
310
+ * @returns {string[]} distinct relative link paths, in document order
311
+ */
312
+ export function extractRelativeLinks(md) {
313
+ if (typeof md !== "string") return [];
314
+ const out = [];
315
+ const seen = new Set();
316
+ const re = /\]\(([^)]+)\)/g;
317
+ let m;
318
+ while ((m = re.exec(md))) {
319
+ let link = m[1].trim();
320
+ // Markdown link can carry a title: ](path "Title") — drop it.
321
+ link = link.replace(/\s+["'].*["']$/, "");
322
+ if (/^(https?:|mailto:|tel:|data:|#)/i.test(link)) continue;
323
+ link = link.split("#")[0].split("?")[0];
324
+ if (!link) continue;
325
+ if (seen.has(link)) continue;
326
+ seen.add(link);
327
+ out.push(link);
328
+ }
329
+ return out;
330
+ }
331
+
332
+ /**
333
+ * Find README relative links that do not resolve to a real path.
334
+ *
335
+ * @param {object} [opts]
336
+ * @param {string} [opts.cwd=REPO_ROOT]
337
+ * @returns {{ checked: number, broken: string[] }}
338
+ */
339
+ export function findBrokenReadmeLinks(opts = {}) {
340
+ const cwd = opts.cwd || REPO_ROOT;
341
+ const readme = path.join(cwd, "README.md");
342
+ if (!existsSync(readme)) return { checked: 0, broken: [] };
343
+ const md = readFileSync(readme, "utf8");
344
+ const links = extractRelativeLinks(md);
345
+ const broken = [];
346
+ for (const link of links) {
347
+ let rel = link;
348
+ try {
349
+ rel = decodeURIComponent(link);
350
+ } catch {
351
+ /* keep raw if it isn't valid percent-encoding */
352
+ }
353
+ const abs = path.resolve(cwd, rel);
354
+ if (!existsSync(abs)) broken.push(link);
355
+ }
356
+ return { checked: links.length, broken };
357
+ }
358
+
359
+ // ── Top-level analysis (pure-ish: reads disk, no process state) ─────────────
360
+
361
+ /**
362
+ * Run every docs-accuracy analysis and return a structured report. Severity is
363
+ * applied by the caller (`run()`); this returns ALL drift it can see.
364
+ *
365
+ * @param {object} [opts] @param {string} [opts.cwd=REPO_ROOT]
366
+ * @returns {Promise<{
367
+ * counts: { agentsActual: number, toolsActual: number,
368
+ * agentClaims: Array<{file:string, claimed:number}>,
369
+ * toolClaims: Array<{file:string, claimed:number}>,
370
+ * agentDrift: Array<{file:string, claimed:number, actual:number}>,
371
+ * toolDrift: Array<{file:string, claimed:number, actual:number}> },
372
+ * danglingTeamRefs: Array<{file:string, agent:string}>,
373
+ * danglingWorkflowRefs: Array<{file:string, agent:string}>,
374
+ * links: { checked: number, broken: string[] },
375
+ * }>}
376
+ */
377
+ export async function analyzeDocsAccuracy(opts = {}) {
378
+ const cwd = opts.cwd || REPO_ROOT;
379
+
380
+ const agentsActual = countAgentDirs(cwd);
381
+ const toolsActual = await countTools(cwd);
382
+
383
+ const docFiles = ["README.md", "CLAUDE.md"];
384
+ const agentClaims = [];
385
+ const toolClaims = [];
386
+ for (const rel of docFiles) {
387
+ const abs = path.join(cwd, rel);
388
+ if (!existsSync(abs)) continue;
389
+ let body;
390
+ try {
391
+ body = readFileSync(abs, "utf8");
392
+ } catch {
393
+ continue;
394
+ }
395
+ for (const c of extractAgentCountClaims(body)) {
396
+ agentClaims.push({ file: rel, claimed: c });
397
+ }
398
+ for (const c of extractToolCountClaims(body)) {
399
+ toolClaims.push({ file: rel, claimed: c });
400
+ }
401
+ }
402
+
403
+ const agentDrift = agentClaims
404
+ .filter((c) => c.claimed !== agentsActual)
405
+ .map((c) => ({ ...c, actual: agentsActual }));
406
+ const toolDrift = toolClaims
407
+ .filter((c) => c.claimed !== toolsActual)
408
+ .map((c) => ({ ...c, actual: toolsActual }));
409
+
410
+ const ids = agentIdSet(cwd);
411
+ const { teamRefs, workflowRefs } = findDanglingAgentRefs({ cwd, ids });
412
+ const links = findBrokenReadmeLinks({ cwd });
413
+
414
+ return {
415
+ counts: {
416
+ agentsActual,
417
+ toolsActual,
418
+ agentClaims,
419
+ toolClaims,
420
+ agentDrift,
421
+ toolDrift,
422
+ },
423
+ danglingTeamRefs: teamRefs,
424
+ danglingWorkflowRefs: workflowRefs,
425
+ links,
426
+ };
427
+ }
428
+
429
+ /**
430
+ * Run the guard and print a human report.
431
+ *
432
+ * Hard-fails (exit 1) ONLY on broken README links — the invariant that is clean
433
+ * today and so safe to ratchet. Count drift and dangling agent refs print as
434
+ * WARN (see the module header for the rationale).
435
+ *
436
+ * @param {string} [cwd=REPO_ROOT]
437
+ * @returns {Promise<number>} exit code (0 ok, 1 broken README link)
438
+ */
439
+ export async function run(cwd = REPO_ROOT) {
440
+ const r = await analyzeDocsAccuracy({ cwd });
441
+ const warnings = [];
442
+
443
+ for (const d of r.counts.agentDrift) {
444
+ warnings.push(
445
+ `${d.file}: claims ${d.claimed} agent definitions, but agents/ has ${d.actual}`,
446
+ );
447
+ }
448
+ for (const d of r.counts.toolDrift) {
449
+ warnings.push(
450
+ `${d.file}: claims ${d.claimed} tools, but tool-definitions has ${d.actual}`,
451
+ );
452
+ }
453
+ for (const t of r.danglingTeamRefs) {
454
+ warnings.push(`${t.file}: team member "${t.agent}" has no agents/${t.agent}/`);
455
+ }
456
+ for (const w of r.danglingWorkflowRefs) {
457
+ warnings.push(
458
+ `${w.file}: workflow agent "${w.agent}" has no agents/${w.agent}/ (LLM-dispatch ref)`,
459
+ );
460
+ }
461
+
462
+ if (warnings.length) {
463
+ console.warn("check-docs-accuracy: WARN — docs drift (non-fatal):");
464
+ for (const w of warnings) console.warn(` ${w}`);
465
+ }
466
+
467
+ if (r.links.broken.length) {
468
+ console.error("check-docs-accuracy: FAIL — README relative links broken:");
469
+ for (const l of r.links.broken) console.error(` ${l}`);
470
+ return 1;
471
+ }
472
+
473
+ console.log(
474
+ `check-docs-accuracy: OK (${r.links.checked} README link(s) resolve; ` +
475
+ `${r.counts.agentsActual} agents, ${r.counts.toolsActual} tools` +
476
+ (warnings.length ? `; ${warnings.length} warning(s)` : "") +
477
+ ")",
478
+ );
479
+ return 0;
480
+ }
481
+
482
+ // Run when invoked directly (not when imported).
483
+ if (import.meta.url === `file://${process.argv[1]}`) {
484
+ run()
485
+ .then((code) => process.exit(code))
486
+ .catch((err) => {
487
+ console.error(
488
+ "check-docs-accuracy: ERROR",
489
+ err && err.message ? err.message : err,
490
+ );
491
+ process.exit(2);
492
+ });
493
+ }