@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,74 @@
1
+ /**
2
+ * lib/env-compat.mjs — NEOLITH_* ⇄ COHORT_* environment bridge.
3
+ *
4
+ * The Neolith→Cohort rename (f3082db) moved every brand env read site to
5
+ * COHORT_* (~86 sites: COHORT_API_KEY, COHORT_ORG_ID, COHORT_BASE, COHORT_DB,
6
+ * COHORT_ADMIN_TOKEN, …), but the installed fleet of Mac-mini agents still
7
+ * launches maestro binaries with the NEOLITH_* names baked into launchd
8
+ * plists, operator shells, and .env files. This module is the runtime layer a
9
+ * text rename cannot ship: one bidirectional alias pass over the environment,
10
+ * so that
11
+ *
12
+ * - a fleet host exporting only NEOLITH_* keeps working against code that
13
+ * reads COHORT_* (legacy → canonical), and
14
+ * - a freshly-provisioned host exporting only COHORT_* keeps working for
15
+ * any older installed binary or spawned subprocess that still reads
16
+ * NEOLITH_* — children inherit the bridged env (canonical → legacy).
17
+ *
18
+ * Suffixes map 1:1 (the rename was textual): NEOLITH_API_KEY ⇄ COHORT_API_KEY.
19
+ * Never clobbers: a key that is already PRESENT — even set to "" — is left
20
+ * byte-for-byte untouched (`in` presence check, not truthiness), so an
21
+ * operator who deliberately sets both names to different values sees no
22
+ * surprise. Idempotent: a second pass finds every twin present and no-ops.
23
+ *
24
+ * NO import-time side effect — maestro modules are side-effect-free at import
25
+ * by convention. Every process entry (bin/maestro.mjs, bin/cohort-mcp.mjs,
26
+ * services/cohort/bin/cohort.mjs, the env-reading scripts/*.mjs) and the SDK
27
+ * entry lib/org/client.mjs — which agent bins import without ever passing
28
+ * through maestro's own entrypoints — call applyBrandEnvCompat() explicitly
29
+ * at the top, before any env-first config resolution.
30
+ *
31
+ * Zero deps — not even node builtins. ESM.
32
+ *
33
+ * @module lib/env-compat
34
+ */
35
+
36
+ "use strict";
37
+
38
+ /** Legacy brand prefix the installed fleet still exports. */
39
+ export const LEGACY_PREFIX = "NEOLITH_";
40
+
41
+ /** Canonical brand prefix the codebase reads post-rename. */
42
+ export const CANONICAL_PREFIX = "COHORT_";
43
+
44
+ /**
45
+ * Mirror every `NEOLITH_*` key onto its `COHORT_*` twin and vice versa,
46
+ * in place, skipping any twin that already exists (never clobbers; presence
47
+ * is `in`-based so an explicit empty string beats an alias). Safe to call
48
+ * repeatedly — the second call is a no-op.
49
+ *
50
+ * @param {NodeJS.ProcessEnv|Record<string,string|undefined>} [env=process.env]
51
+ * Environment map to bridge in place; injectable for deterministic tests.
52
+ * @returns {{from:string,to:string}[]} the aliases created, in application
53
+ * order — [] when nothing needed bridging (and always [] on a re-run).
54
+ */
55
+ export function applyBrandEnvCompat(env = process.env) {
56
+ const created = [];
57
+ if (!env || typeof env !== "object") return created;
58
+ for (const key of Object.keys(env)) {
59
+ let twin;
60
+ if (key.startsWith(LEGACY_PREFIX)) {
61
+ twin = CANONICAL_PREFIX + key.slice(LEGACY_PREFIX.length);
62
+ } else if (key.startsWith(CANONICAL_PREFIX)) {
63
+ twin = LEGACY_PREFIX + key.slice(CANONICAL_PREFIX.length);
64
+ } else {
65
+ continue;
66
+ }
67
+ if (twin in env) continue; // never clobber — presence wins, even ""
68
+ const value = env[key];
69
+ if (typeof value !== "string") continue; // never mint "undefined" strings
70
+ env[twin] = value;
71
+ created.push({ from: key, to: twin });
72
+ }
73
+ return created;
74
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * env-compat.test.mjs — NEOLITH_* ⇄ COHORT_* bridge: both directions,
3
+ * no-clobber (presence beats aliasing, even for ""), non-brand keys
4
+ * untouched, idempotency, and the default process.env path. Uses injected
5
+ * plain-object envs everywhere except the one save/restore process.env test.
6
+ * Run: node --test lib/env-compat.test.mjs
7
+ */
8
+ "use strict";
9
+
10
+ import { test } from "node:test";
11
+ import assert from "node:assert/strict";
12
+
13
+ import { applyBrandEnvCompat, LEGACY_PREFIX, CANONICAL_PREFIX } from "./env-compat.mjs";
14
+
15
+ test("legacy → canonical: NEOLITH_* is mirrored to COHORT_*", () => {
16
+ const env = { NEOLITH_API_KEY: "nlk_123", NEOLITH_ORG_ID: "acme" };
17
+ const created = applyBrandEnvCompat(env);
18
+ assert.equal(env.COHORT_API_KEY, "nlk_123");
19
+ assert.equal(env.COHORT_ORG_ID, "acme");
20
+ assert.equal(env.NEOLITH_API_KEY, "nlk_123"); // source untouched
21
+ assert.deepEqual(
22
+ created.sort((a, b) => a.from.localeCompare(b.from)),
23
+ [
24
+ { from: "NEOLITH_API_KEY", to: "COHORT_API_KEY" },
25
+ { from: "NEOLITH_ORG_ID", to: "COHORT_ORG_ID" },
26
+ ],
27
+ );
28
+ });
29
+
30
+ test("canonical → legacy: COHORT_* is mirrored to NEOLITH_*", () => {
31
+ const env = { COHORT_DB: "/var/db/cohort.sqlite", COHORT_ADMIN_TOKEN: "adm_1" };
32
+ applyBrandEnvCompat(env);
33
+ assert.equal(env.NEOLITH_DB, "/var/db/cohort.sqlite");
34
+ assert.equal(env.NEOLITH_ADMIN_TOKEN, "adm_1");
35
+ });
36
+
37
+ test("never clobbers: a present twin is left byte-for-byte untouched", () => {
38
+ const env = {
39
+ NEOLITH_API_KEY: "old-value",
40
+ COHORT_API_KEY: "new-value", // both set, deliberately different
41
+ NEOLITH_BASE: "https://legacy.example",
42
+ COHORT_BASE: "", // present-but-empty still wins over the alias
43
+ };
44
+ const created = applyBrandEnvCompat(env);
45
+ assert.equal(env.COHORT_API_KEY, "new-value");
46
+ assert.equal(env.NEOLITH_API_KEY, "old-value");
47
+ assert.equal(env.COHORT_BASE, ""); // NOT overwritten by NEOLITH_BASE
48
+ assert.deepEqual(created, []); // every twin existed — nothing bridged
49
+ });
50
+
51
+ test("presence propagates: an empty-string source mirrors as empty string", () => {
52
+ const env = { NEOLITH_AGENT_ID: "" };
53
+ applyBrandEnvCompat(env);
54
+ assert.equal(env.COHORT_AGENT_ID, "");
55
+ });
56
+
57
+ test("non-brand keys and non-string values are ignored", () => {
58
+ const env = {
59
+ PATH: "/usr/bin",
60
+ NEOLITHIC_ERA: "not-a-brand-var", // prefix must match exactly
61
+ COHORTS: "plural, not the prefix",
62
+ NEOLITH_BROKEN: undefined, // never minted into "undefined"
63
+ };
64
+ const created = applyBrandEnvCompat(env);
65
+ assert.deepEqual(created, []);
66
+ assert.ok(!("COHORT_BROKEN" in env));
67
+ assert.ok(!("COHORTIC_ERA" in env));
68
+ assert.equal(Object.keys(env).length, 4);
69
+ });
70
+
71
+ test("idempotent: a second pass is a no-op that reports []", () => {
72
+ const env = { NEOLITH_API_TOKEN: "t1", COHORT_REPO: "/path/to/hq" };
73
+ const first = applyBrandEnvCompat(env);
74
+ assert.equal(first.length, 2);
75
+ const snapshot = { ...env };
76
+ const second = applyBrandEnvCompat(env);
77
+ assert.deepEqual(second, []);
78
+ assert.deepEqual(env, snapshot);
79
+ });
80
+
81
+ test("defaults to process.env and bridges the real environment", () => {
82
+ // Unique suffix so a concurrent test file can never collide.
83
+ const suffix = `ENV_COMPAT_TEST_${process.pid}`;
84
+ const legacyKey = `${LEGACY_PREFIX}${suffix}`;
85
+ const canonicalKey = `${CANONICAL_PREFIX}${suffix}`;
86
+ const prevLegacy = process.env[legacyKey];
87
+ const prevCanonical = process.env[canonicalKey];
88
+ try {
89
+ delete process.env[canonicalKey];
90
+ process.env[legacyKey] = "bridged";
91
+ applyBrandEnvCompat();
92
+ assert.equal(process.env[canonicalKey], "bridged");
93
+ } finally {
94
+ if (prevLegacy == null) delete process.env[legacyKey];
95
+ else process.env[legacyKey] = prevLegacy;
96
+ if (prevCanonical == null) delete process.env[canonicalKey];
97
+ else process.env[canonicalKey] = prevCanonical;
98
+ }
99
+ });
100
+
101
+ test("tolerates a missing/foreign env argument without throwing", () => {
102
+ assert.deepEqual(applyBrandEnvCompat(null), []);
103
+ assert.deepEqual(applyBrandEnvCompat("not-an-object"), []);
104
+ });
@@ -0,0 +1,331 @@
1
+ /**
2
+ * Maestro — Feature Init Tracking
3
+ *
4
+ * Pairs the framework's `framework-features.json` (versioned registry) with
5
+ * each agent's `.maestro/features.json` (per-agent installed state) so
6
+ * `maestro upgrade` and `maestro init` can:
7
+ *
8
+ * 1. Detect new framework features the agent has not yet initialised.
9
+ * 2. Auto-run low-risk init steps (mkdir, scaffold, copy).
10
+ * 3. Defer init steps that need user input (RAG repo selection, backup
11
+ * credentials) onto a `pending` queue.
12
+ * 4. Surface the pending queue via a SessionStart hook banner so the
13
+ * operator (or Claude) sees "run `maestro init` to complete setup"
14
+ * every time a Claude Code session opens in the agent's directory.
15
+ *
16
+ * Storage:
17
+ * ~/maestro/framework-features.json ← framework SOT
18
+ * ~/<agent>/.maestro/features.json ← agent's installed state
19
+ *
20
+ * Agent file shape:
21
+ * {
22
+ * "framework_version": "1.9.0",
23
+ * "schema_version": "1",
24
+ * "last_upgrade": "2026-05-12T10:00:00Z",
25
+ * "initialized": {
26
+ * "<feature-name>": { "version": "1", "initialized_at": "..." }
27
+ * },
28
+ * "pending": [
29
+ * { "feature": "...", "added_at": "...", "version": "1" }
30
+ * ]
31
+ * }
32
+ *
33
+ * The library is dep-free (only node:fs / node:path / node:child_process)
34
+ * and exits cleanly on missing files — fresh agents that haven't been
35
+ * upgraded yet simply have no `.maestro/features.json`.
36
+ */
37
+
38
+ import {
39
+ existsSync,
40
+ mkdirSync,
41
+ readFileSync,
42
+ writeFileSync,
43
+ } from "node:fs";
44
+ import { join, resolve, dirname } from "node:path";
45
+ import { spawnSync } from "node:child_process";
46
+
47
+ export const FEATURE_REGISTRY_RELATIVE = "framework-features.json";
48
+ export const AGENT_STATE_RELATIVE = ".maestro/features.json";
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Path resolution
52
+ // ---------------------------------------------------------------------------
53
+
54
+ export function resolveAgentRoot(agentRoot) {
55
+ return resolve(agentRoot || process.env.AGENT_ROOT || process.env.AGENT_DIR || process.cwd());
56
+ }
57
+
58
+ export function getStatePath(agentRoot) {
59
+ return join(resolveAgentRoot(agentRoot), AGENT_STATE_RELATIVE);
60
+ }
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Registry / state loaders
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Load the framework feature registry. Pass the absolute path to
68
+ * `framework-features.json` (typically resolved relative to the maestro
69
+ * CLI entry point). Returns the parsed registry or throws if missing.
70
+ */
71
+ export function loadRegistry(registryPath) {
72
+ if (!existsSync(registryPath)) {
73
+ throw new Error(`framework-features.json not found at ${registryPath}`);
74
+ }
75
+ try {
76
+ return JSON.parse(readFileSync(registryPath, "utf-8"));
77
+ } catch (err) {
78
+ throw new Error(`failed to parse framework-features.json: ${err.message}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Load the per-agent install state. Returns a fresh empty state if the
84
+ * file doesn't exist yet — callers should treat that as "every feature is
85
+ * pending init for the first time".
86
+ */
87
+ export function loadAgentState(agentRoot) {
88
+ const statePath = getStatePath(agentRoot);
89
+ if (!existsSync(statePath)) {
90
+ return {
91
+ framework_version: null,
92
+ schema_version: "1",
93
+ last_upgrade: null,
94
+ initialized: {},
95
+ pending: [],
96
+ };
97
+ }
98
+ try {
99
+ const raw = JSON.parse(readFileSync(statePath, "utf-8"));
100
+ // Defensive defaults so partially-written state still works.
101
+ raw.initialized = raw.initialized || {};
102
+ raw.pending = Array.isArray(raw.pending) ? raw.pending : [];
103
+ raw.schema_version = raw.schema_version || "1";
104
+ return raw;
105
+ } catch {
106
+ return {
107
+ framework_version: null,
108
+ schema_version: "1",
109
+ last_upgrade: null,
110
+ initialized: {},
111
+ pending: [],
112
+ };
113
+ }
114
+ }
115
+
116
+ export function saveAgentState(agentRoot, state) {
117
+ const statePath = getStatePath(agentRoot);
118
+ mkdirSync(dirname(statePath), { recursive: true });
119
+ writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Diff between registry and agent state
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /**
127
+ * Return a list of features that need initialisation. A feature is "needs
128
+ * init" when the registry's `version` is strictly newer than the agent's
129
+ * recorded version, OR when the agent has no entry at all.
130
+ *
131
+ * Returns: [{ name, version, definition, status: "new" | "upgraded" }]
132
+ */
133
+ export function diffFeatures(registry, agentState) {
134
+ const out = [];
135
+ const installed = agentState.initialized || {};
136
+ for (const [name, def] of Object.entries(registry.features || {})) {
137
+ const current = installed[name];
138
+ if (!current) {
139
+ out.push({ name, version: def.version, definition: def, status: "new" });
140
+ continue;
141
+ }
142
+ if (String(current.version) !== String(def.version)) {
143
+ out.push({ name, version: def.version, definition: def, status: "upgraded" });
144
+ }
145
+ }
146
+ return out;
147
+ }
148
+
149
+ // ---------------------------------------------------------------------------
150
+ // Init runner
151
+ // ---------------------------------------------------------------------------
152
+
153
+ /**
154
+ * Reconcile the agent's state with the registry. For each diffed feature:
155
+ * - if `init.auto === true` and `runAuto`, execute init.command (spawn
156
+ * /bin/bash -lc so wrappers / npx scripts resolve correctly).
157
+ * - else, add to pending list.
158
+ *
159
+ * Options:
160
+ * maestroRoot absolute path to ~/maestro (used to resolve init scripts)
161
+ * agentRoot absolute path to the agent's repo
162
+ * registry parsed registry (loaded by loadRegistry)
163
+ * runAuto boolean; default true. Set false during dry-run/plan modes.
164
+ * logger optional fn({ stage, feature, status, message })
165
+ *
166
+ * Returns: { ranAuto: [...], pending: [...], failed: [...] }
167
+ */
168
+ export function reconcileFeatures({ maestroRoot, agentRoot, registry, runAuto = true, logger } = {}) {
169
+ const root = resolveAgentRoot(agentRoot);
170
+ const state = loadAgentState(root);
171
+ const diff = diffFeatures(registry, state);
172
+ const result = { ranAuto: [], pending: [], failed: [] };
173
+ const log = logger || (() => {});
174
+ const now = new Date().toISOString();
175
+
176
+ for (const item of diff) {
177
+ const init = item.definition.init || {};
178
+ const isAuto = init.auto === true;
179
+
180
+ if (isAuto && runAuto) {
181
+ log({ stage: "running", feature: item.name, message: init.description });
182
+ const ran = runInitCommand({ maestroRoot, agentRoot: root, command: init.command });
183
+ if (ran.ok) {
184
+ state.initialized[item.name] = { version: String(item.version), initialized_at: now };
185
+ result.ranAuto.push(item.name);
186
+ log({ stage: "completed", feature: item.name });
187
+ } else {
188
+ result.failed.push({ name: item.name, error: ran.error });
189
+ // Push to pending so user can retry manually.
190
+ pushPending(state, item, now);
191
+ log({ stage: "failed", feature: item.name, message: ran.error });
192
+ }
193
+ } else {
194
+ pushPending(state, item, now);
195
+ result.pending.push(item.name);
196
+ log({ stage: "pending", feature: item.name, message: init.description });
197
+ }
198
+ }
199
+
200
+ // After reconciliation, prune any pending entries that are now
201
+ // initialised (e.g. user ran the init manually).
202
+ state.pending = (state.pending || []).filter((p) => !state.initialized[p.feature]);
203
+
204
+ state.framework_version = registry.framework_version || state.framework_version;
205
+ state.last_upgrade = now;
206
+ saveAgentState(root, state);
207
+ return result;
208
+ }
209
+
210
+ function pushPending(state, item, now) {
211
+ const existing = (state.pending || []).find((p) => p.feature === item.name);
212
+ if (existing) {
213
+ existing.version = String(item.version);
214
+ existing.added_at = existing.added_at || now;
215
+ return;
216
+ }
217
+ state.pending = state.pending || [];
218
+ state.pending.push({
219
+ feature: item.name,
220
+ version: String(item.version),
221
+ added_at: now,
222
+ title: item.definition.title || item.name,
223
+ description: item.definition.init?.description || "",
224
+ command: item.definition.init?.command || "",
225
+ });
226
+ }
227
+
228
+ /**
229
+ * Run an init command in the agent repo.
230
+ *
231
+ * ──────────────────────────────────────────────────────────────────────────
232
+ * TRUST BOUNDARY (audit L20) — READ BEFORE CHANGING.
233
+ *
234
+ * `command` MUST originate ONLY from the trusted framework feature registry
235
+ * (`framework-features.json`, loaded by {@link loadRegistry}). That file is
236
+ * shipped with maestro and version-controlled; its `init.command` strings are
237
+ * authored by framework maintainers, NOT by end users, inbound messages, or
238
+ * any other runtime input.
239
+ *
240
+ * The generic-shell fallback below executes `command` via `/bin/bash -lc`,
241
+ * which is shell-INTERPRETED (unlike the rest of maestro, which uses
242
+ * execFile/spawn with argv arrays per the project's no-shell convention). That
243
+ * is acceptable ONLY because the input is trusted-by-provenance. If you ever
244
+ * wire a caller that lets untrusted data reach `command`, you MUST instead
245
+ * pass an argv array to spawnSync without a shell (or an allowlist) — do NOT
246
+ * extend the `/bin/bash -lc` path to untrusted input.
247
+ *
248
+ * As defence-in-depth, this function refuses an obviously-untrusted command:
249
+ * a non-string, or a string carrying NUL / newline / carriage-return bytes
250
+ * (which a legitimate single-line registry command never contains and which
251
+ * are classic shell-injection smuggling vectors). This guard does not weaken
252
+ * the trusted path; it only fails closed on input that could not have come
253
+ * from a well-formed registry entry.
254
+ *
255
+ * Accepts simple shell strings so init scripts can rely on PATH having
256
+ * homebrew/nvm bins. Always runs with cwd = agentRoot and inherits the
257
+ * caller's env plus AGENT_ROOT.
258
+ * ──────────────────────────────────────────────────────────────────────────
259
+ */
260
+ function runInitCommand({ maestroRoot, agentRoot, command }) {
261
+ if (!command || command === "true") return { ok: true };
262
+ // Defence-in-depth guard (audit L20): a well-formed registry init.command is
263
+ // a single-line string. Anything else is treated as untrusted and refused
264
+ // rather than handed to the shell.
265
+ if (typeof command !== "string" || /[\0\r\n]/.test(command)) {
266
+ return { ok: false, error: "refused untrusted init command (must be a single-line string from the framework registry)" };
267
+ }
268
+ // If the command names a path that exists relative to maestroRoot,
269
+ // resolve to its absolute path so the init script doesn't depend on
270
+ // the agent having a copy of the script already.
271
+ const parts = command.split(/\s+/);
272
+ // Support: `node scripts/setup/init-xxx.mjs`
273
+ if (parts[0] === "node" && parts[1]) {
274
+ const candidateAgent = join(agentRoot, parts[1]);
275
+ const candidateFramework = maestroRoot ? join(maestroRoot, parts[1]) : null;
276
+ let scriptPath = null;
277
+ if (existsSync(candidateAgent)) scriptPath = candidateAgent;
278
+ else if (candidateFramework && existsSync(candidateFramework)) scriptPath = candidateFramework;
279
+ if (scriptPath) {
280
+ const args = [scriptPath, ...parts.slice(2)];
281
+ const r = spawnSync(process.execPath, args, {
282
+ cwd: agentRoot,
283
+ env: { ...process.env, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot, MAESTRO_ROOT: maestroRoot || "" },
284
+ encoding: "utf-8",
285
+ });
286
+ if (r.status === 0) return { ok: true, stdout: r.stdout };
287
+ return { ok: false, error: (r.stderr || `exit ${r.status}`).trim() };
288
+ }
289
+ return { ok: false, error: `init script not found: ${parts[1]}` };
290
+ }
291
+ // Generic shell fallback. SHELL-INTERPRETED — `command` MUST be trusted
292
+ // (framework-registry-sourced) per the TRUST BOUNDARY note above. The guard
293
+ // at the top of this function has already rejected obviously-untrusted input.
294
+ const r = spawnSync("/bin/bash", ["-lc", command], {
295
+ cwd: agentRoot,
296
+ env: { ...process.env, AGENT_ROOT: agentRoot, AGENT_DIR: agentRoot, MAESTRO_ROOT: maestroRoot || "" },
297
+ encoding: "utf-8",
298
+ });
299
+ if (r.status === 0) return { ok: true, stdout: r.stdout };
300
+ return { ok: false, error: (r.stderr || `exit ${r.status}`).trim() };
301
+ }
302
+
303
+ // ---------------------------------------------------------------------------
304
+ // Pending queue accessors (used by banner + `maestro init`)
305
+ // ---------------------------------------------------------------------------
306
+
307
+ export function listPending(agentRoot) {
308
+ const state = loadAgentState(agentRoot);
309
+ return state.pending || [];
310
+ }
311
+
312
+ export function markInitialised(agentRoot, featureName, version) {
313
+ const state = loadAgentState(agentRoot);
314
+ state.initialized = state.initialized || {};
315
+ state.initialized[featureName] = {
316
+ version: String(version),
317
+ initialized_at: new Date().toISOString(),
318
+ };
319
+ state.pending = (state.pending || []).filter((p) => p.feature !== featureName);
320
+ saveAgentState(agentRoot, state);
321
+ }
322
+
323
+ export function bannerSnapshot(agentRoot) {
324
+ const state = loadAgentState(agentRoot);
325
+ return {
326
+ pending_count: (state.pending || []).length,
327
+ pending: state.pending || [],
328
+ last_upgrade: state.last_upgrade,
329
+ framework_version: state.framework_version,
330
+ };
331
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * fs-atomic.mjs — durable, atomic filesystem write primitives.
3
+ *
4
+ * Many maestro writers (self.json, org-state JSONLs, registry/presence files,
5
+ * cost ledgers) currently use a bare `writeFileSync`, which can leave a
6
+ * zero-length or half-written file if the process dies mid-write — exactly the
7
+ * corruption hazard the state-integrity audit flagged. This module is the one
8
+ * shared home for the write-temp → fsync → rename primitive so every durable
9
+ * writer gets crash-safety the same way (SPEC-org-server Phase 0; roadmap 0.15).
10
+ *
11
+ * The pattern (proven in lib/cadence-bus.mjs `writeJsonAtomic`): write a sibling
12
+ * `.tmp` file, fsync its CONTENTS to disk, then `rename(2)` it over the target.
13
+ * The fsync happens BEFORE the rename so the durable bytes are in place once the
14
+ * atomic rename publishes the name — a crash after rename returns cannot surface
15
+ * a partial file. On macOS APFS (and Linux ext4/xfs) rename(2) is atomic for a
16
+ * same-directory source/dest.
17
+ *
18
+ * Node builtins only (maestro is "type":"module", near-zero-dep by design).
19
+ *
20
+ * @module lib/fs-atomic
21
+ */
22
+
23
+ import {
24
+ mkdirSync,
25
+ openSync,
26
+ writeSync,
27
+ fsyncSync,
28
+ closeSync,
29
+ renameSync,
30
+ unlinkSync,
31
+ appendFileSync,
32
+ } from "node:fs";
33
+ import { dirname } from "node:path";
34
+ import { randomBytes } from "node:crypto";
35
+
36
+ /**
37
+ * Build a collision-resistant sibling temp path for `targetPath`. Keeping the
38
+ * temp file in the SAME directory guarantees the subsequent rename is
39
+ * same-volume (and therefore atomic), unlike a temp in os.tmpdir().
40
+ * @param {string} targetPath
41
+ * @returns {string}
42
+ */
43
+ function tmpSibling(targetPath) {
44
+ return `${targetPath}.tmp.${process.pid}.${Date.now()}.${randomBytes(4).toString("hex")}`;
45
+ }
46
+
47
+ /**
48
+ * Atomically write `data` (string or Buffer) to `targetPath`, creating parent
49
+ * dirs as needed. Durable: contents are fsync'd before the rename publishes the
50
+ * name. Throws on failure (after cleaning up the temp file).
51
+ *
52
+ * @param {string} targetPath
53
+ * @param {string|Buffer} data
54
+ * @param {object} [opts]
55
+ * @param {import("node:fs").Mode} [opts.mode] file mode for the created file
56
+ * @returns {void}
57
+ */
58
+ export function writeFileAtomic(targetPath, data, opts = {}) {
59
+ mkdirSync(dirname(targetPath), { recursive: true });
60
+ const tmp = tmpSibling(targetPath);
61
+ const fd = openSync(tmp, "w", opts.mode);
62
+ try {
63
+ writeSync(fd, data);
64
+ fsyncSync(fd);
65
+ } finally {
66
+ closeSync(fd);
67
+ }
68
+ try {
69
+ renameSync(tmp, targetPath);
70
+ } catch (err) {
71
+ try { unlinkSync(tmp); } catch { /* ignore orphan-cleanup failure */ }
72
+ throw err;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Atomically write `obj` as pretty-printed JSON (+ trailing newline) to
78
+ * `targetPath`. The canonical writer for self.json / config / state snapshots.
79
+ *
80
+ * @param {string} targetPath
81
+ * @param {unknown} obj
82
+ * @param {object} [opts] @param {import("node:fs").Mode} [opts.mode]
83
+ * @returns {void}
84
+ */
85
+ export function writeJsonAtomic(targetPath, obj, opts = {}) {
86
+ writeFileAtomic(targetPath, JSON.stringify(obj, null, 2) + "\n", opts);
87
+ }
88
+
89
+ /**
90
+ * Append one JSON record as a single line to a JSONL file (append-only logs:
91
+ * audit trails, org-event streams, ledgers). Creates parent dirs as needed.
92
+ *
93
+ * A single `appendFileSync` of a sub-PIPE_BUF line is the standard atomic-append
94
+ * idiom for an O_APPEND write on a local filesystem; we serialize the object
95
+ * once and write it in a single call so concurrent appenders interleave whole
96
+ * lines, never partial ones. Returns true on success; on IO failure returns
97
+ * false rather than throwing, so a best-effort audit append can never crash the
98
+ * caller's main path (callers that need hard durability should check the return).
99
+ *
100
+ * @param {string} targetPath
101
+ * @param {unknown} record
102
+ * @returns {boolean} true if the line was written
103
+ */
104
+ export function appendJsonl(targetPath, record) {
105
+ try {
106
+ mkdirSync(dirname(targetPath), { recursive: true });
107
+ appendFileSync(targetPath, JSON.stringify(record) + "\n");
108
+ return true;
109
+ } catch {
110
+ return false;
111
+ }
112
+ }