@cohortapp/agent-sdk 2.17.0 → 2.18.4

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 (526) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +18 -5
  3. package/README.md +1 -0
  4. package/bin/maestro.mjs +62 -0
  5. package/docs/guides/billing-console-keys.md +60 -0
  6. package/docs/guides/front-door-session.md +54 -9
  7. package/docs/guides/mac-mini.md +20 -25
  8. package/docs/guides/setup-wizard.md +1 -1
  9. package/docs/runbooks/fleet-rollout.md +156 -0
  10. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  11. package/lib/action-executor.js +19 -3
  12. package/lib/budget-guard.mjs +279 -3
  13. package/lib/channels/base-adapter.mjs +3 -1
  14. package/lib/channels/contract.mjs +2 -1
  15. package/lib/channels/inbox-item.mjs +8 -0
  16. package/lib/claude-bin.mjs +5 -6
  17. package/lib/cli/doctor-checks.mjs +141 -10
  18. package/lib/cli/global-setup-extras.mjs +5 -1
  19. package/lib/cli/inbox.mjs +100 -15
  20. package/lib/cli/seat-auth.mjs +463 -0
  21. package/lib/cli/session.mjs +80 -12
  22. package/lib/collective/capture.mjs +8 -6
  23. package/lib/collective/global-config.mjs +63 -1
  24. package/lib/collective/presence.mjs +142 -5
  25. package/lib/comms/send-gate.mjs +559 -1
  26. package/lib/diagnostics/alerts.mjs +49 -0
  27. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  28. package/lib/engine/agents/definitions.mjs +343 -0
  29. package/lib/engine/agents/persist.mjs +275 -0
  30. package/lib/engine/agents/runtime.mjs +748 -0
  31. package/lib/engine/agents/usage.mjs +95 -0
  32. package/lib/engine/auth-status.mjs +139 -0
  33. package/lib/engine/budget.mjs +194 -0
  34. package/lib/engine/cli.mjs +1204 -0
  35. package/lib/engine/commands/index.mjs +269 -0
  36. package/lib/engine/context/budget.mjs +219 -0
  37. package/lib/engine/context/cache.mjs +125 -0
  38. package/lib/engine/context/child-env.mjs +215 -0
  39. package/lib/engine/context/compaction.mjs +342 -0
  40. package/lib/engine/context/images.mjs +90 -0
  41. package/lib/engine/context/instructions.mjs +327 -0
  42. package/lib/engine/context/lazy-instructions.mjs +169 -0
  43. package/lib/engine/context/manager.mjs +182 -0
  44. package/lib/engine/context/real-path.mjs +91 -0
  45. package/lib/engine/context/secret-values.mjs +163 -0
  46. package/lib/engine/context/settings.mjs +274 -0
  47. package/lib/engine/context/stream-input.mjs +159 -0
  48. package/lib/engine/guard.mjs +152 -0
  49. package/lib/engine/hooks.mjs +713 -0
  50. package/lib/engine/loop.mjs +560 -0
  51. package/lib/engine/mcp/client.mjs +254 -0
  52. package/lib/engine/mcp/config.mjs +301 -0
  53. package/lib/engine/mcp/http.mjs +201 -0
  54. package/lib/engine/mcp/index.mjs +146 -0
  55. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  56. package/lib/engine/mcp/naming.mjs +66 -0
  57. package/lib/engine/mcp/resources.mjs +89 -0
  58. package/lib/engine/mcp/results.mjs +133 -0
  59. package/lib/engine/mcp/stdio.mjs +137 -0
  60. package/lib/engine/mcp/supervisor.mjs +116 -0
  61. package/lib/engine/messages.mjs +104 -0
  62. package/lib/engine/output/json.mjs +164 -0
  63. package/lib/engine/output/stream-json.mjs +266 -0
  64. package/lib/engine/permissions.mjs +845 -0
  65. package/lib/engine/process-identity.mjs +164 -0
  66. package/lib/engine/process-tree.mjs +551 -0
  67. package/lib/engine/prompt.mjs +60 -0
  68. package/lib/engine/session/store.mjs +299 -0
  69. package/lib/engine/session-runtime/args.mjs +97 -0
  70. package/lib/engine/session-runtime/host.mjs +143 -0
  71. package/lib/engine/session-runtime/inbox.mjs +122 -0
  72. package/lib/engine/session-runtime/notifications.mjs +129 -0
  73. package/lib/engine/session-runtime/registry.mjs +328 -0
  74. package/lib/engine/session-runtime/runner.mjs +344 -0
  75. package/lib/engine/session-runtime/socket.mjs +212 -0
  76. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  77. package/lib/engine/skills/index.mjs +321 -0
  78. package/lib/engine/tools/bash-background.mjs +533 -0
  79. package/lib/engine/tools/bash.mjs +216 -0
  80. package/lib/engine/tools/edit.mjs +97 -0
  81. package/lib/engine/tools/glob.mjs +81 -0
  82. package/lib/engine/tools/grep.mjs +224 -0
  83. package/lib/engine/tools/index.mjs +84 -0
  84. package/lib/engine/tools/list-agents.mjs +32 -0
  85. package/lib/engine/tools/ls.mjs +127 -0
  86. package/lib/engine/tools/monitor.mjs +82 -0
  87. package/lib/engine/tools/notebook-edit.mjs +218 -0
  88. package/lib/engine/tools/read.mjs +103 -0
  89. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  90. package/lib/engine/tools/schema.mjs +144 -0
  91. package/lib/engine/tools/send-message.mjs +77 -0
  92. package/lib/engine/tools/session.mjs +70 -0
  93. package/lib/engine/tools/todo.mjs +144 -0
  94. package/lib/engine/tools/toolsearch.mjs +217 -0
  95. package/lib/engine/tools/walk.mjs +193 -0
  96. package/lib/engine/tools/web-switch.mjs +31 -0
  97. package/lib/engine/tools/webfetch-html.mjs +387 -0
  98. package/lib/engine/tools/webfetch-net.mjs +340 -0
  99. package/lib/engine/tools/webfetch.mjs +198 -0
  100. package/lib/engine/tools/websearch.mjs +91 -0
  101. package/lib/engine/tools/workflow.mjs +95 -0
  102. package/lib/engine/tools/write.mjs +76 -0
  103. package/lib/engine/tui/line-editor.mjs +137 -0
  104. package/lib/engine/tui/render.mjs +86 -0
  105. package/lib/engine/tui/tui.mjs +274 -0
  106. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  107. package/lib/engine/wire/effort.mjs +36 -0
  108. package/lib/engine/wire/errors.mjs +496 -0
  109. package/lib/engine/wire/http.mjs +441 -0
  110. package/lib/engine/wire/index.mjs +76 -0
  111. package/lib/engine/wire/openai-chat.mjs +332 -0
  112. package/lib/engine/wire/prompt-cache.mjs +79 -0
  113. package/lib/engine/wire/search.mjs +140 -0
  114. package/lib/engine/wire/sse.mjs +114 -0
  115. package/lib/engine/wire/stall.mjs +349 -0
  116. package/lib/engine/wire/token-provider.mjs +175 -0
  117. package/lib/engine/wire/usage.mjs +192 -0
  118. package/lib/engine/workflow/host.mjs +524 -0
  119. package/lib/engine/workflow/journal.mjs +188 -0
  120. package/lib/engine/workflow/json-schema.mjs +171 -0
  121. package/lib/engine/workflow/meta.mjs +329 -0
  122. package/lib/engine/workflow/notifications.mjs +52 -0
  123. package/lib/engine/workflow/runtime.mjs +447 -0
  124. package/lib/engine/workflow/sandbox.mjs +534 -0
  125. package/lib/engine/workflow/worker.mjs +141 -0
  126. package/lib/engine/workflow/worktree.mjs +74 -0
  127. package/lib/execution/disposition.mjs +1 -1
  128. package/lib/execution/intake.mjs +10 -0
  129. package/lib/execution/surface-policy.mjs +15 -0
  130. package/lib/learning/curator.mjs +8 -6
  131. package/lib/learning/reflect.mjs +8 -6
  132. package/lib/model-router/catalog/cohort.yaml +137 -0
  133. package/lib/model-router/catalog.mjs +118 -1
  134. package/lib/model-router/failover.mjs +67 -16
  135. package/lib/model-router/llm-task.mjs +39 -3
  136. package/lib/model-router/resolve.mjs +89 -3
  137. package/lib/model-router/spawn.mjs +46 -47
  138. package/lib/model-router/taxonomy.mjs +126 -4
  139. package/lib/org/cost-sync.mjs +141 -11
  140. package/lib/org/inbound/broadcast.mjs +289 -0
  141. package/lib/org/inbound/collective.mjs +375 -0
  142. package/lib/org/inbound/directedness.mjs +96 -8
  143. package/lib/org/inbound/facts.mjs +78 -2
  144. package/lib/org/inbound/project.mjs +22 -0
  145. package/lib/org/inbound/surfaces.mjs +14 -0
  146. package/lib/org/llm-token.mjs +879 -0
  147. package/lib/org/mesh.mjs +61 -0
  148. package/lib/org/messaging.mjs +3 -1
  149. package/lib/org/protocol.checksum +1 -1
  150. package/lib/org/protocol.mjs +15 -0
  151. package/lib/org/quota.mjs +520 -0
  152. package/lib/org/tool-surface.mjs +104 -16
  153. package/lib/org/ui-parity.mjs +16 -1
  154. package/lib/org/work-ledger.mjs +37 -6
  155. package/lib/rate-guard.mjs +114 -1
  156. package/lib/resource-governor.mjs +41 -6
  157. package/lib/runtime/adapter.mjs +823 -0
  158. package/lib/runtime/child-env.mjs +191 -0
  159. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  160. package/lib/runtime/seat-engine.mjs +162 -0
  161. package/lib/session/ask-ledger.mjs +271 -0
  162. package/lib/session/current-work.mjs +676 -0
  163. package/lib/session/feed-core.mjs +40 -3
  164. package/lib/session/launch-args.mjs +56 -4
  165. package/lib/session/status-summary.mjs +26 -9
  166. package/lib/session/upgrade-notice.mjs +42 -0
  167. package/lib/setup/claude-probe.mjs +117 -13
  168. package/lib/setup/enrich.mjs +13 -10
  169. package/lib/setup/sections/model.mjs +39 -13
  170. package/lib/telemetry/collect.mjs +208 -9
  171. package/lib/upgrade/ignored-drift.mjs +105 -0
  172. package/lib/voice/post-call-brief.mjs +30 -17
  173. package/package.json +13 -3
  174. package/plugins/maestro-skills/skills/board-work.md +5 -0
  175. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  176. package/plugins/maestro-skills/skills/main-session.md +18 -7
  177. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  178. package/scripts/ci/run-tests.mjs +47 -19
  179. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  180. package/scripts/collective/hook-runner.mjs +29 -2
  181. package/scripts/continuous-monitor.sh +13 -0
  182. package/scripts/cost/track-claude-usage.mjs +15 -0
  183. package/scripts/daemon/agent-daemon.mjs +408 -20
  184. package/scripts/daemon/assurance.mjs +48 -12
  185. package/scripts/daemon/cadence-consumer.mjs +218 -68
  186. package/scripts/daemon/cadence-handlers.mjs +73 -4
  187. package/scripts/daemon/classifier.mjs +75 -26
  188. package/scripts/daemon/context-compiler.mjs +51 -37
  189. package/scripts/daemon/deliver.mjs +30 -1
  190. package/scripts/daemon/dispatcher.mjs +595 -149
  191. package/scripts/daemon/health.mjs +14 -1
  192. package/scripts/daemon/maestro-daemon.mjs +11 -0
  193. package/scripts/daemon/prompt-builder.mjs +24 -0
  194. package/scripts/daemon/responder.mjs +246 -79
  195. package/scripts/daemon/sdk-version.mjs +98 -16
  196. package/scripts/eval/probe-gateway.mjs +635 -0
  197. package/scripts/eval/replay/extract.mjs +270 -0
  198. package/scripts/eval/replay/grade.mjs +260 -0
  199. package/scripts/eval/replay/lib/config.mjs +50 -0
  200. package/scripts/eval/replay/lib/effects.mjs +65 -0
  201. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  202. package/scripts/eval/replay/lib/judge.mjs +72 -0
  203. package/scripts/eval/replay/lib/redact.mjs +136 -0
  204. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  205. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  206. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  207. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  208. package/scripts/eval/replay/report.mjs +185 -0
  209. package/scripts/eval/replay/run.mjs +404 -0
  210. package/scripts/fleet/rollout.mjs +1094 -0
  211. package/scripts/hooks/pre-send-audit.sh +36 -245
  212. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  213. package/scripts/hooks/validate-state-yaml.sh +190 -0
  214. package/scripts/huddle/huddle-llm.mjs +361 -0
  215. package/scripts/huddle/huddle-server.mjs +46 -121
  216. package/scripts/local-triggers/autoupdate.sh +448 -78
  217. package/scripts/local-triggers/run-trigger.sh +13 -0
  218. package/scripts/maintenance/pin-integrity.mjs +364 -0
  219. package/scripts/poll-slack-events.sh +41 -9
  220. package/scripts/poller/slack-socket-mode.mjs +28 -3
  221. package/scripts/session/supervisor.mjs +80 -13
  222. package/scripts/spawn-session.sh +13 -0
  223. package/bin/maestro.test.mjs +0 -1574
  224. package/lib/action-executor.test.mjs +0 -871
  225. package/lib/archetype.test.mjs +0 -132
  226. package/lib/assurance/plan-note.test.mjs +0 -234
  227. package/lib/assurance/room-budget.test.mjs +0 -486
  228. package/lib/assurance/tier.test.mjs +0 -174
  229. package/lib/autonomy.test.mjs +0 -66
  230. package/lib/backlog.test.mjs +0 -302
  231. package/lib/backup/policy.test.mjs +0 -305
  232. package/lib/budget-escalate.test.mjs +0 -232
  233. package/lib/budget-guard.envelope.test.mjs +0 -476
  234. package/lib/budget-guard.test.mjs +0 -427
  235. package/lib/cadence-bus-requeue.test.mjs +0 -83
  236. package/lib/cadence-bus-schedule.test.mjs +0 -194
  237. package/lib/cadence-bus.test.mjs +0 -720
  238. package/lib/cadences.test.mjs +0 -230
  239. package/lib/capability/inventory.test.mjs +0 -232
  240. package/lib/capability.test.mjs +0 -78
  241. package/lib/channels/base-adapter.test.mjs +0 -590
  242. package/lib/channels/channels.test.mjs +0 -371
  243. package/lib/channels/contract.test.mjs +0 -162
  244. package/lib/channels/inbox-item.test.mjs +0 -368
  245. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  246. package/lib/channels/pairing.test.mjs +0 -270
  247. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  248. package/lib/channels/slack-adapter.test.mjs +0 -212
  249. package/lib/channels/telegram-adapter.test.mjs +0 -306
  250. package/lib/channels/voice/adapter.test.mjs +0 -278
  251. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  252. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  253. package/lib/charter.test.mjs +0 -89
  254. package/lib/claude-bin.test.mjs +0 -131
  255. package/lib/cli/board.test.mjs +0 -227
  256. package/lib/cli/design.test.mjs +0 -270
  257. package/lib/cli/doctor-checks.test.mjs +0 -336
  258. package/lib/cli/global-setup-extras.test.mjs +0 -462
  259. package/lib/cli/inbox.test.mjs +0 -230
  260. package/lib/cli/session-ack.test.mjs +0 -63
  261. package/lib/cli/session.test.mjs +0 -613
  262. package/lib/collective/capture.test.mjs +0 -121
  263. package/lib/collective/cards.test.mjs +0 -114
  264. package/lib/collective/config.test.mjs +0 -123
  265. package/lib/collective/global-config.test.mjs +0 -220
  266. package/lib/collective/global-skills.test.mjs +0 -126
  267. package/lib/collective/presence.test.mjs +0 -95
  268. package/lib/collective/recall.test.mjs +0 -116
  269. package/lib/collective/vendor-skills.test.mjs +0 -306
  270. package/lib/comms/send-gate.test.mjs +0 -770
  271. package/lib/comms.test.mjs +0 -41
  272. package/lib/context/budget.test.mjs +0 -252
  273. package/lib/context/history-scope.test.mjs +0 -79
  274. package/lib/cost/ledger-row.test.mjs +0 -183
  275. package/lib/design/design-md.test.mjs +0 -318
  276. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  277. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  278. package/lib/design/fixtures/foundation.json +0 -133
  279. package/lib/design/refresh-gate.test.mjs +0 -144
  280. package/lib/design/write.test.mjs +0 -241
  281. package/lib/diagnostics/alerts.test.mjs +0 -318
  282. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  283. package/lib/diagnostics/counters.test.mjs +0 -206
  284. package/lib/diagnostics/events.test.mjs +0 -290
  285. package/lib/diagnostics/otel.test.mjs +0 -196
  286. package/lib/diagnostics/trace.test.mjs +0 -251
  287. package/lib/env-compat.test.mjs +0 -104
  288. package/lib/execution/disposition.test.mjs +0 -553
  289. package/lib/execution/drive.test.mjs +0 -270
  290. package/lib/execution/effects.test.mjs +0 -344
  291. package/lib/execution/intake.test.mjs +0 -389
  292. package/lib/execution/journal.test.mjs +0 -261
  293. package/lib/execution/match.test.mjs +0 -235
  294. package/lib/execution/pipeline.test.mjs +0 -392
  295. package/lib/execution/route.test.mjs +0 -186
  296. package/lib/execution/surface-policy.test.mjs +0 -162
  297. package/lib/fs-atomic.test.mjs +0 -72
  298. package/lib/fs-ownership.test.mjs +0 -158
  299. package/lib/goals/admission.test.mjs +0 -164
  300. package/lib/goals/classify.test.mjs +0 -167
  301. package/lib/goals/collaborate.test.mjs +0 -336
  302. package/lib/goals/gaps.test.mjs +0 -284
  303. package/lib/goals/loop.test.mjs +0 -845
  304. package/lib/hooks/bus.test.mjs +0 -387
  305. package/lib/identity/persona.test.mjs +0 -142
  306. package/lib/kpi-sensors.test.mjs +0 -278
  307. package/lib/kpi.test.mjs +0 -244
  308. package/lib/learning/config.test.mjs +0 -75
  309. package/lib/learning/counters.test.mjs +0 -69
  310. package/lib/learning/curator-consolidate.test.mjs +0 -238
  311. package/lib/learning/curator.test.mjs +0 -106
  312. package/lib/learning/reflect.test.mjs +0 -0
  313. package/lib/learning/session-index.test.mjs +0 -125
  314. package/lib/learning/skill-writer.test.mjs +0 -210
  315. package/lib/mandate/audit.test.mjs +0 -195
  316. package/lib/mandate/contract.test.mjs +0 -185
  317. package/lib/mandate/derive.test.mjs +0 -274
  318. package/lib/mandate/model.test.mjs +0 -164
  319. package/lib/mandate/refresh.test.mjs +0 -389
  320. package/lib/mcp/server.test.mjs +0 -426
  321. package/lib/model-router/auth-profiles.test.mjs +0 -580
  322. package/lib/model-router/catalog.test.mjs +0 -385
  323. package/lib/model-router/economics.test.mjs +0 -438
  324. package/lib/model-router/failover.test.mjs +0 -439
  325. package/lib/model-router/health.test.mjs +0 -338
  326. package/lib/model-router/integration-coverage.test.mjs +0 -831
  327. package/lib/model-router/integration.test.mjs +0 -564
  328. package/lib/model-router/ledger.test.mjs +0 -415
  329. package/lib/model-router/llm-task.test.mjs +0 -392
  330. package/lib/model-router/org-credentials.test.mjs +0 -265
  331. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  332. package/lib/model-router/reconcile.test.mjs +0 -316
  333. package/lib/model-router/repair.test.mjs +0 -180
  334. package/lib/model-router/spawn.test.mjs +0 -446
  335. package/lib/model-router/taxonomy.test.mjs +0 -410
  336. package/lib/model-router.test.mjs +0 -1207
  337. package/lib/org/activity.test.mjs +0 -134
  338. package/lib/org/approvals.test.mjs +0 -216
  339. package/lib/org/awareness.test.mjs +0 -159
  340. package/lib/org/board-mine-cache.test.mjs +0 -53
  341. package/lib/org/board.test.mjs +0 -187
  342. package/lib/org/bootstrap-context.test.mjs +0 -153
  343. package/lib/org/client.test.mjs +0 -1206
  344. package/lib/org/cohort-client.test.mjs +0 -126
  345. package/lib/org/cost-sync.test.mjs +0 -153
  346. package/lib/org/doctor.test.mjs +0 -346
  347. package/lib/org/engagement-ledger.test.mjs +0 -112
  348. package/lib/org/engagement.test.mjs +0 -739
  349. package/lib/org/handoff.test.mjs +0 -269
  350. package/lib/org/inbound/directedness.test.mjs +0 -668
  351. package/lib/org/inbound/facts.test.mjs +0 -471
  352. package/lib/org/inbound/hydrate.test.mjs +0 -908
  353. package/lib/org/inbound/index.test.mjs +0 -429
  354. package/lib/org/inbound/project.test.mjs +0 -287
  355. package/lib/org/integration-tools.test.mjs +0 -160
  356. package/lib/org/keys.test.mjs +0 -92
  357. package/lib/org/knowledge.test.mjs +0 -326
  358. package/lib/org/leases.test.mjs +0 -235
  359. package/lib/org/mesh-directives.test.mjs +0 -110
  360. package/lib/org/mesh-integration.test.mjs +0 -127
  361. package/lib/org/mesh.test.mjs +0 -400
  362. package/lib/org/messaging.test.mjs +0 -471
  363. package/lib/org/param-contract.test.mjs +0 -477
  364. package/lib/org/policy.test.mjs +0 -237
  365. package/lib/org/protocol.checksum.test.mjs +0 -90
  366. package/lib/org/protocol.test.mjs +0 -323
  367. package/lib/org/push.test.mjs +0 -792
  368. package/lib/org/registry.test.mjs +0 -100
  369. package/lib/org/resource-tools.test.mjs +0 -361
  370. package/lib/org/tool-access.test.mjs +0 -144
  371. package/lib/org/tool-surface-integration.test.mjs +0 -120
  372. package/lib/org/tool-surface.test.mjs +0 -1268
  373. package/lib/org/typing.test.mjs +0 -291
  374. package/lib/org/ui-parity.test.mjs +0 -560
  375. package/lib/org/verify.test.mjs +0 -194
  376. package/lib/org/work-ledger.test.mjs +0 -273
  377. package/lib/plan/adoption-e2e.test.mjs +0 -366
  378. package/lib/plan/budget-enforcement.test.mjs +0 -400
  379. package/lib/plan/compile.test.mjs +0 -382
  380. package/lib/plan/emit.test.mjs +0 -269
  381. package/lib/plan/explain.test.mjs +0 -188
  382. package/lib/prompts/parallelism.test.mjs +0 -177
  383. package/lib/rag/rag.test.mjs +0 -505
  384. package/lib/rate-guard.test.mjs +0 -272
  385. package/lib/reactive-gate.test.mjs +0 -57
  386. package/lib/render.test.mjs +0 -68
  387. package/lib/resource-governor.test.mjs +0 -488
  388. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  389. package/lib/scheduling/jitter.test.mjs +0 -140
  390. package/lib/secrets/broker.test.mjs +0 -280
  391. package/lib/secrets/providers.test.mjs +0 -274
  392. package/lib/security/audit-engine.test.mjs +0 -424
  393. package/lib/security/coerce-args.test.mjs +0 -281
  394. package/lib/security/dangerous-tools.test.mjs +0 -68
  395. package/lib/security/external-content.test.mjs +0 -84
  396. package/lib/security/redact.test.mjs +0 -441
  397. package/lib/security/secret-equal.test.mjs +0 -55
  398. package/lib/session/config.test.mjs +0 -92
  399. package/lib/session/feed-core.test.mjs +0 -198
  400. package/lib/session/first-run.test.mjs +0 -121
  401. package/lib/session/frontdoor.test.mjs +0 -205
  402. package/lib/session/handoffs.test.mjs +0 -183
  403. package/lib/session/identity.test.mjs +0 -180
  404. package/lib/session/inbox-claims.test.mjs +0 -286
  405. package/lib/session/launch-args.test.mjs +0 -157
  406. package/lib/session/liveness.test.mjs +0 -100
  407. package/lib/session/status-summary.test.mjs +0 -118
  408. package/lib/session-permissions.test.mjs +0 -120
  409. package/lib/setup/claude-probe.test.mjs +0 -187
  410. package/lib/setup/completeness.test.mjs +0 -110
  411. package/lib/setup/context-pack.test.mjs +0 -89
  412. package/lib/setup/enrich.test.mjs +0 -115
  413. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  414. package/lib/setup/integration.test.mjs +0 -162
  415. package/lib/setup/io.test.mjs +0 -77
  416. package/lib/setup/runner.test.mjs +0 -132
  417. package/lib/setup/sections/identity.test.mjs +0 -234
  418. package/lib/setup/sections/inventory.test.mjs +0 -198
  419. package/lib/setup/sections/learning.test.mjs +0 -81
  420. package/lib/setup/sections/mandate.test.mjs +0 -388
  421. package/lib/setup/sections/messaging.test.mjs +0 -127
  422. package/lib/setup/sections/model.test.mjs +0 -240
  423. package/lib/setup/sections/org.test.mjs +0 -346
  424. package/lib/setup/sections/orgmail.test.mjs +0 -118
  425. package/lib/setup/sections/recovery.test.mjs +0 -98
  426. package/lib/setup/sections/subagents.test.mjs +0 -429
  427. package/lib/setup/sections/verify.test.mjs +0 -175
  428. package/lib/setup/sot.test.mjs +0 -81
  429. package/lib/setup/state.test.mjs +0 -115
  430. package/lib/singleton.test.mjs +0 -151
  431. package/lib/subagents/cli.test.mjs +0 -389
  432. package/lib/subagents/client.test.mjs +0 -309
  433. package/lib/subagents/gap.test.mjs +0 -234
  434. package/lib/subagents/lock.test.mjs +0 -248
  435. package/lib/subagents/manifest.test.mjs +0 -175
  436. package/lib/subagents/refs.test.mjs +0 -204
  437. package/lib/subagents/resolve.test.mjs +0 -422
  438. package/lib/subagents/schema.test.mjs +0 -328
  439. package/lib/telemetry/alerts.test.mjs +0 -109
  440. package/lib/telemetry/collect.test.mjs +0 -1274
  441. package/lib/tool-definitions-integration.test.mjs +0 -83
  442. package/lib/tool-definitions.test.mjs +0 -437
  443. package/lib/upgrade/global-refresh.test.mjs +0 -65
  444. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  445. package/lib/upgrade/post-steps.test.mjs +0 -200
  446. package/lib/upgrade/verify.test.mjs +0 -164
  447. package/lib/util/fetch-timeout.test.mjs +0 -202
  448. package/lib/util/reconnect.test.mjs +0 -369
  449. package/lib/util/unhandled.test.mjs +0 -216
  450. package/lib/voice/outbound.test.mjs +0 -69
  451. package/lib/voice/session-rotation.test.mjs +0 -114
  452. package/lib/voice/stt.test.mjs +0 -226
  453. package/lib/voice/voice.test.mjs +0 -990
  454. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  455. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  456. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  457. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  458. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  459. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  460. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  461. package/scripts/ci/check.test.mjs +0 -194
  462. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  463. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  464. package/scripts/collective/hook-runner.test.mjs +0 -173
  465. package/scripts/cost/fleet-digest.test.mjs +0 -207
  466. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  467. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  468. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  469. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  470. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  471. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  472. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  473. package/scripts/daemon/assurance.test.mjs +0 -1791
  474. package/scripts/daemon/board-mirror.test.mjs +0 -165
  475. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  476. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  477. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  478. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  479. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  480. package/scripts/daemon/classifier.test.mjs +0 -266
  481. package/scripts/daemon/classify-kind.test.mjs +0 -40
  482. package/scripts/daemon/context-compiler.test.mjs +0 -406
  483. package/scripts/daemon/deliver.test.mjs +0 -564
  484. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  485. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  486. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  487. package/scripts/daemon/dispatcher-session-continuity.test.mjs +0 -365
  488. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  489. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  490. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  491. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  492. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  493. package/scripts/daemon/integration.test.mjs +0 -149
  494. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  495. package/scripts/daemon/lib/session-router.test.mjs +0 -554
  496. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  497. package/scripts/daemon/prompt-builder.test.mjs +0 -556
  498. package/scripts/daemon/responder-cost.test.mjs +0 -68
  499. package/scripts/daemon/responder-history.test.mjs +0 -221
  500. package/scripts/daemon/sdk-version.test.mjs +0 -31
  501. package/scripts/daemon/session-lock.test.mjs +0 -252
  502. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  503. package/scripts/daemon/typing-registry.test.mjs +0 -102
  504. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  505. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  506. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  507. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  508. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  509. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  510. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  511. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  512. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  513. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  514. package/scripts/poller-launchd/install.test.mjs +0 -243
  515. package/scripts/restore-from-backup.test.mjs +0 -181
  516. package/scripts/session/feed.test.mjs +0 -196
  517. package/scripts/session/supervisor-sh.test.mjs +0 -218
  518. package/scripts/session/supervisor.test.mjs +0 -482
  519. package/scripts/setup/configure-macos.test.mjs +0 -306
  520. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  521. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  522. package/scripts/setup/generate-capability.test.mjs +0 -134
  523. package/scripts/setup/init-agent.test.mjs +0 -370
  524. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  525. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  526. package/scripts/watchdog/memory-watchdog.test.mjs +0 -64
@@ -0,0 +1,91 @@
1
+ /**
2
+ * lib/engine/context/real-path.mjs — where a path really leads (CF-19, W5-B).
3
+ *
4
+ * Permission path rules and the engine-config protection judge a file by the
5
+ * place a tool call would really touch, not by the name the model gave it: a
6
+ * symlink inside the working directory that points at ~/.ssh, /etc or
7
+ * ~/.claude/settings.json is judged as that target (lib/engine/permissions.mjs).
8
+ *
9
+ * `resolveRealPath(target)`:
10
+ * · an existing path → its realpath (every symlink on the way resolved);
11
+ * · a dangling symlink → the path its link text names, resolved again (a
12
+ * Write through it creates that file);
13
+ * · a path that does not exist yet → the realpath of its nearest existing
14
+ * ancestor joined with the rest (a new file under a symlinked directory);
15
+ * · a symlink loop, or a chain longer than the hop budget → null (nothing
16
+ * can be opened through it). Never throws.
17
+ *
18
+ * The file-system calls are injectable, so the walk is testable with fakes.
19
+ * Checking is not locking: a link swapped between the check and the tool's
20
+ * open is not caught (docs/engine/README.md, Permissions).
21
+ *
22
+ * @module lib/engine/context/real-path
23
+ */
24
+
25
+ import { lstatSync, readlinkSync, realpathSync } from "node:fs";
26
+ import path from "node:path";
27
+
28
+ /** How many path steps (links followed and ancestors walked) one resolution may take. */
29
+ export const MAX_RESOLVE_STEPS = 160;
30
+
31
+ /**
32
+ * @typedef {Object} RealPathFs
33
+ * @property {(p:string)=>string} [realpath]
34
+ * @property {(p:string)=>{isSymbolicLink:()=>boolean}} [lstat]
35
+ * @property {(p:string)=>string} [readlink]
36
+ */
37
+
38
+ /**
39
+ * @param {string} target absolute (a relative path is resolved against process.cwd())
40
+ * @param {RealPathFs} [fs]
41
+ * @returns {string|null}
42
+ */
43
+ export function resolveRealPath(target, fs = {}) {
44
+ const realpath = fs.realpath ?? ((/** @type string */ p) => realpathSync.native(p));
45
+ const lstat = fs.lstat ?? lstatSync;
46
+ const readlink = fs.readlink ?? ((/** @type string */ p) => readlinkSync(p, "utf8"));
47
+ let steps = 0;
48
+ /** @param {string} p @returns {string|null} */
49
+ const walk = (p) => {
50
+ if (++steps > MAX_RESOLVE_STEPS) return null;
51
+ try {
52
+ return realpath(p);
53
+ } catch (e) {
54
+ if (/** @type {NodeJS.ErrnoException} */ (e)?.code === "ELOOP") return null;
55
+ /* ENOENT, ENOTDIR, EACCES: look at the path itself, then at its parent */
56
+ }
57
+ let isLink = false;
58
+ try {
59
+ isLink = lstat(p).isSymbolicLink();
60
+ } catch {
61
+ isLink = false;
62
+ }
63
+ if (isLink) {
64
+ let text;
65
+ try {
66
+ text = readlink(p);
67
+ } catch {
68
+ return null;
69
+ }
70
+ return walk(path.resolve(path.dirname(p), text));
71
+ }
72
+ const parent = path.dirname(p);
73
+ if (parent === p) return p;
74
+ const real = walk(parent);
75
+ return real === null ? null : path.join(real, path.basename(p));
76
+ };
77
+ try {
78
+ return walk(path.resolve(String(target)));
79
+ } catch {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ /**
85
+ * A resolver for the permission engine (`evaluatePermission({resolvePath})`).
86
+ * @param {RealPathFs} [fs]
87
+ * @returns {(p:string)=>string|null}
88
+ */
89
+ export function createPathResolver(fs = {}) {
90
+ return (p) => resolveRealPath(p, fs);
91
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * lib/engine/context/secret-values.mjs — does an environment VALUE look like a
3
+ * credential, whatever its variable is called (CF-118, W5-B)?
4
+ *
5
+ * The name scrub (tools/bash.mjs SECRET_ENV and EXTENDED_SECRET_ENV) misses a
6
+ * credential whose name says nothing: `DATABASE_URL=postgres://app:pw@db/x`,
7
+ * `REDIS_URL`, `AMQP_URL`, `SENTRY_DSN`, a token stored as `UPSTREAM`. This
8
+ * module judges the value. `credentialValueKind(value, {name})` returns the
9
+ * first rule that matches, or null:
10
+ *
11
+ * url-userinfo scheme://user:PASSWORD@host… anywhere in the value — a
12
+ * non-empty password is required (`https://user@host` and
13
+ * `http://host:8080/` are not credentials)
14
+ * url-secret a URL carrying a signing or token query parameter
15
+ * (`?token=`, `?access_token=`, `?key=`, `?sig=`,
16
+ * `X-Amz-Signature=`, … with a value of 16+ characters), or a
17
+ * Slack or Discord webhook URL
18
+ * private-key a PEM private key block (`-----BEGIN … PRIVATE KEY-----`)
19
+ * token-prefix a vendor token shape: `sk-…`, `sk_live_…`, `ghp_…`/`gho_…`/
20
+ * `ghu_…`/`ghs_…`/`ghr_…`, `github_pat_…`, `glpat-…`,
21
+ * `xox[abpr]-…`, `xapp-…`, `AKIA…`/`ASIA…` (16 more), `AIza…`,
22
+ * `npm_…`, `hf_…`, and Cohort's own `cst_…` / `nlk_…`
23
+ * jwt three base64url parts, the first `eyJ…`
24
+ * high-entropy a run of 32+ characters of the base64/base64url/token
25
+ * alphabet holding lower case, upper case AND digits, with a
26
+ * Shannon entropy of at least 4.0 bits per character; a
27
+ * leading `word_` (≤ 12 letters: `session_`, `msg_`) is not
28
+ * counted in the length, so prefixed ids stay short
29
+ * hex the whole value is 32+ hex digits
30
+ *
31
+ * The thresholds, and why they are conservative. Random base64 of 24+ bytes
32
+ * (32 characters) measures ~4.4–5.0 bits per character; the ordinary values
33
+ * found in a seat's environment measure less or fail the character-class test:
34
+ * paths (`/`, `~` or `.` first; searched segment by segment), PATH-like lists
35
+ * (`:` separates), `NODE_OPTIONS` (no upper case), UUIDs (excluded by shape),
36
+ * subresource-integrity strings (`sha512-…`, excluded by prefix), prefixed ids
37
+ * (`session_01…`, prefix not counted). Hex is judged apart: a 40-character git
38
+ * SHA and a 32-character hex API key look the same, so a hex value is withheld
39
+ * unless its NAME says it is a hash or an id (`GIT_COMMIT`, `GITHUB_SHA`,
40
+ * `*_DIGEST`, `*_CHECKSUM`, `*_REV`, `*_ID`, `npm_package_gitHead`, …;
41
+ * {@link HASH_OR_ID_NAME_RE}). The same name exemption applies to the
42
+ * high-entropy rule — never to the URL, key, vendor-prefix or JWT rules, which
43
+ * name a credential by their shape alone.
44
+ *
45
+ * Not caught (a narrowing, not a boundary): a short or low-entropy password
46
+ * under an ordinary name (`DB_PW=hunter2`), a secret split across two variables
47
+ * each below the length threshold, a hex or random secret under a hash- or
48
+ * id-shaped name, a value encoded with an alphabet outside the token one
49
+ * (percent-encoding with `%`), and anything a command reads from a file.
50
+ * Base64- or hex-wrapped secrets of 24+ bytes ARE caught (by the high-entropy
51
+ * and hex rules).
52
+ *
53
+ * Pure: no I/O, no clock, no randomness.
54
+ *
55
+ * @module lib/engine/context/secret-values
56
+ */
57
+
58
+ /** A value this long, of the token alphabet, with all three character classes, is measured. */
59
+ export const HIGH_ENTROPY_MIN_LENGTH = 32;
60
+ /** Shannon entropy per character at or above which such a run is a credential. */
61
+ export const HIGH_ENTROPY_MIN_BITS = 4.0;
62
+ /** Such a run reads as random, not as words, when its letters hold at most this share of vowels … */
63
+ export const MAX_RANDOM_VOWEL_SHARE = 0.3;
64
+ /** … or its digits fall in at least this many separate runs. */
65
+ export const MIN_RANDOM_DIGIT_RUNS = 3;
66
+ /** A whole value of this many hex digits or more is a credential unless its name is hash- or id-shaped. */
67
+ export const HEX_MIN_LENGTH = 32;
68
+
69
+ /**
70
+ * Names whose hex or high-entropy value is an identifier, not a credential:
71
+ * a whole `_`-separated word (`GIT_COMMIT`, `BUILD_ID`, `IMAGE_DIGEST`) or a
72
+ * camel-case tail (`npm_package_gitHead`).
73
+ */
74
+ export const HASH_OR_ID_NAME_RE = /(?:^|_)(?:SHA\d*|COMMIT|REV|REVISION|HASH|DIGEST|CHECKSUM|FINGERPRINT|INTEGRITY|ETAG|ID|IDS|UUID|GUID|NONCE|TRACE|SPAN|VERSION|BUILD|HEAD|REF)(?:_|$)|(?:[a-z](?:Sha|Hash|Head|Commit|Id|Digest))$/;
75
+
76
+ const URL_USERINFO_RE = /\b[a-z][a-z0-9+.-]*:\/\/[^\s/?#@:]*:[^\s/?#@]+@[^\s/?#@]/i;
77
+ const URL_SECRET_PARAM_RE = /[?&](?:access_token|refresh_token|id_token|token|auth|api_?key|key|secret|client_secret|password|passwd|sig|signature|x-amz-signature|x-amz-credential|x-amz-security-token|x-goog-signature|x-goog-credential)=([^&#\s]{16,})/i;
78
+ const WEBHOOK_RE = /https:\/\/(?:hooks\.slack\.com\/(?:services|workflows|triggers)\/[A-Za-z0-9/_-]{20,}|(?:ptb\.|canary\.)?discord(?:app)?\.com\/api\/webhooks\/\d+\/[A-Za-z0-9_-]{20,})/i;
79
+ const PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----/;
80
+ const JWT_RE = /(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/;
81
+ /** Vendor token shapes. Each is bounded on the left so a longer word containing it does not match. */
82
+ const TOKEN_PREFIX_RES = Object.freeze([
83
+ /(?<![A-Za-z0-9_-])sk-[A-Za-z0-9_-]{20,}/, // OpenAI, Anthropic (sk-ant-…), DeepSeek, …
84
+ /(?<![A-Za-z0-9_])(?:sk|rk)_(?:live|test)_[A-Za-z0-9]{16,}/, // Stripe
85
+ /(?<![A-Za-z0-9_])gh[pousr]_[A-Za-z0-9]{30,}/, // GitHub
86
+ /(?<![A-Za-z0-9_])github_pat_[A-Za-z0-9_]{22,}/,
87
+ /(?<![A-Za-z0-9_-])glpat-[A-Za-z0-9_-]{20,}/, // GitLab
88
+ /(?<![A-Za-z0-9_-])xox[abpr]-[A-Za-z0-9-]{10,}/, // Slack
89
+ /(?<![A-Za-z0-9_-])xapp-\d-[A-Za-z0-9-]{10,}/,
90
+ /(?<![A-Za-z0-9])(?:AKIA|ASIA)[0-9A-Z]{16}(?![0-9A-Za-z])/, // AWS access key id
91
+ /(?<![A-Za-z0-9_-])AIza[0-9A-Za-z_-]{35}/, // Google API key
92
+ /(?<![A-Za-z0-9_])npm_[A-Za-z0-9]{36}(?![A-Za-z0-9])/,
93
+ /(?<![A-Za-z0-9_])hf_[A-Za-z0-9]{30,}/, // Hugging Face
94
+ /(?<![A-Za-z0-9_])(?:cst|nlk)_[A-Za-z0-9_-]{16,}/, // Cohort gateway and org keys
95
+ ]);
96
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
97
+ const HEX_RE = /^[0-9a-f]+$/i;
98
+ const TOKEN_ALPHABET_RE = /^[A-Za-z0-9+/=_.~-]+$/;
99
+ const INTEGRITY_PREFIX_RE = /^(?:sha1|sha224|sha256|sha384|sha512|md5)[-:]/i;
100
+ /** Separators between the candidate runs of a value (a `Bearer x`, `a=b;c=d`, a path's segments). */
101
+ const RUN_SPLIT_RE = /[\s,;:=&"'<>()[\]{}|@\\/]+/;
102
+
103
+ /**
104
+ * Shannon entropy of a string, in bits per character. Pure.
105
+ * @param {string} s
106
+ * @returns {number}
107
+ */
108
+ export function shannonBitsPerChar(s) {
109
+ if (s.length === 0) return 0;
110
+ /** @type {Map<string, number>} */
111
+ const counts = new Map();
112
+ for (const ch of s) counts.set(ch, (counts.get(ch) ?? 0) + 1);
113
+ let bits = 0;
114
+ for (const n of counts.values()) {
115
+ const p = n / s.length;
116
+ bits -= p * Math.log2(p);
117
+ }
118
+ return bits;
119
+ }
120
+
121
+ /**
122
+ * One candidate run: long, of the token alphabet, all three classes, random enough.
123
+ * @param {string} run
124
+ */
125
+ function looksRandom(run) {
126
+ if (!TOKEN_ALPHABET_RE.test(run) || UUID_RE.test(run) || INTEGRITY_PREFIX_RE.test(run)) return false;
127
+ const body = run.replace(/^[a-z]{1,12}_/, "");
128
+ if (body.length < HIGH_ENTROPY_MIN_LENGTH) return false;
129
+ if (!/[a-z]/.test(body) || !/[A-Z]/.test(body) || !/[0-9]/.test(body)) return false;
130
+ if (shannonBitsPerChar(body) < HIGH_ENTROPY_MIN_BITS) return false;
131
+ // Words, not noise: a camel-case identifier (`MyLongClassName2024Build`) has English's vowel share
132
+ // and its digits in one or two runs; random letters are ~19% vowels and random digits scatter.
133
+ const letters = body.replace(/[^A-Za-z]/g, "");
134
+ const vowelShare = (letters.match(/[aeiou]/gi)?.length ?? 0) / letters.length;
135
+ const digitRuns = body.match(/[0-9]+/g)?.length ?? 0;
136
+ return vowelShare <= MAX_RANDOM_VOWEL_SHARE || digitRuns >= MIN_RANDOM_DIGIT_RUNS;
137
+ }
138
+
139
+ /**
140
+ * @typedef {'url-userinfo'|'url-secret'|'private-key'|'token-prefix'|'jwt'|'high-entropy'|'hex'} CredentialValueKind
141
+ */
142
+
143
+ /**
144
+ * Which credential shape a value has, if any. Pure.
145
+ * @param {unknown} value
146
+ * @param {{name?:string}} [o] the variable's name: a hash- or id-shaped name exempts the hex and high-entropy rules
147
+ * @returns {CredentialValueKind|null}
148
+ */
149
+ export function credentialValueKind(value, { name = "" } = {}) {
150
+ if (typeof value !== "string" || value.length < 8) return null;
151
+ if (URL_USERINFO_RE.test(value)) return "url-userinfo";
152
+ if (value.includes("://") && (URL_SECRET_PARAM_RE.test(value) || WEBHOOK_RE.test(value))) return "url-secret";
153
+ if (PRIVATE_KEY_RE.test(value)) return "private-key";
154
+ if (TOKEN_PREFIX_RES.some((re) => re.test(value))) return "token-prefix";
155
+ if (JWT_RE.test(value)) return "jwt";
156
+ if (HASH_OR_ID_NAME_RE.test(name)) return null;
157
+ const trimmed = value.trim();
158
+ if (trimmed.length >= HEX_MIN_LENGTH && HEX_RE.test(trimmed) && !UUID_RE.test(trimmed)) return "hex";
159
+ // The whole value (standard base64 may hold `/`), unless it reads as a path; then each run.
160
+ if (!/^[/~.]/.test(trimmed) && looksRandom(trimmed)) return "high-entropy";
161
+ if (trimmed.split(RUN_SPLIT_RE).some((run) => run.length >= HIGH_ENTROPY_MIN_LENGTH && looksRandom(run))) return "high-entropy";
162
+ return null;
163
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * lib/engine/context/settings.mjs — the settings layers a run obeys.
3
+ *
4
+ * Files (Claude-compatible layout), from highest precedence to lowest:
5
+ *
6
+ * managed /Library/Application Support/ClaudeCode/managed-settings.json (macOS)
7
+ * /etc/claude-code/managed-settings.json (Linux)
8
+ * cli --settings <file|json>, then the permission flags
9
+ * local <cwd>/.claude/settings.local.json
10
+ * project <cwd>/.claude/settings.json
11
+ * user ~/.claude/settings.json ($CLAUDE_CONFIG_DIR replaces ~/.claude)
12
+ *
13
+ * Merging:
14
+ * · permissions.allow / deny / ask and additionalDirectories concatenate
15
+ * across layers (rule evaluation is by category, so order within a
16
+ * category never changes a decision);
17
+ * · hooks concatenate per event, each entry remembering its layer;
18
+ * · permissions.defaultMode takes the highest-precedence layer that sets it;
19
+ * a managed defaultMode also beats --permission-mode (the managed layer
20
+ * is the organisation's, the flag is the caller's);
21
+ * · disableBypassPermissionsMode: "disable" in any layer holds;
22
+ * · disableAllHooks is honoured only from the user and managed layers (a
23
+ * project cannot switch off a seat's hooks) and never removes managed
24
+ * hooks; set elsewhere it is ignored with a warning;
25
+ * · project MCP approval — enableAllProjectMcpServers and
26
+ * enabledMcpjsonServers — is honoured only from the managed, cli and user
27
+ * layers: a repository cannot approve its own .mcp.json.
28
+ * disabledMcpjsonServers (a restriction) is honoured from every layer;
29
+ * · `cohort.pluginDirs` (an engine extension, ignored by other tools)
30
+ * concatenates; each entry is a plugin root, `dir/*` expands to its
31
+ * subdirectories;
32
+ * · `env` merges key by key, a higher layer's value winning. It is applied
33
+ * to the environment of tool commands (Bash, subject to its credential
34
+ * scrub), hook commands and stdio MCP servers (subject to theirs) — never
35
+ * to the engine's own gateway configuration;
36
+ * · `model` takes the highest-precedence layer that sets it; it is the
37
+ * run's default tier when neither --model nor COHORT_LLM_MODEL is given;
38
+ * · `apiKeyHelper` is ignored with a warning: the engine's gateway
39
+ * credential is COHORT_LLM_TOKEN;
40
+ * · the engine's own keys under `cohort` — `contextWindows` and `compaction`
41
+ * (objects, merged key by key), `promptCacheKey` and `imageInput`
42
+ * (booleans, highest layer wins) and `webTools` (only `false` counts, from
43
+ * any layer) — are returned as `engine`.
44
+ *
45
+ * Relative paths in a layer resolve against that layer's root: the project
46
+ * directory for project/local, the home directory for user, the working
47
+ * directory for cli, and "/" for managed.
48
+ *
49
+ * `mergeSettings` is pure; `loadSettingsLayers` reads through injected fs.
50
+ *
51
+ * @module lib/engine/context/settings
52
+ */
53
+
54
+ import path from "node:path";
55
+ import { compileRules, mergeRuleSets } from "../permissions.mjs";
56
+
57
+ /** @typedef {'managed'|'cli'|'local'|'project'|'user'} SettingsScope */
58
+ /** @typedef {{scope:SettingsScope, path:string, baseDir:string, settings:any}} SettingsLayer */
59
+
60
+ export const HOOK_EVENTS = Object.freeze(["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop", "SubagentStop", "PreCompact", "Notification"]);
61
+
62
+ /** @param {string} platform */
63
+ export function defaultManagedSettingsPath(platform) {
64
+ if (platform === "darwin") return "/Library/Application Support/ClaudeCode/managed-settings.json";
65
+ if (platform === "linux") return "/etc/claude-code/managed-settings.json";
66
+ return null;
67
+ }
68
+
69
+ /**
70
+ * @param {{homedir:string|null, cwd:string, env?:Record<string,string|undefined>}} p
71
+ * @returns {string|null} the per-user config directory (~/.claude)
72
+ */
73
+ export function userConfigDir({ homedir, env = {} }) {
74
+ if (env.CLAUDE_CONFIG_DIR) return env.CLAUDE_CONFIG_DIR;
75
+ return homedir ? path.join(homedir, ".claude") : null;
76
+ }
77
+
78
+ /**
79
+ * @param {object} p
80
+ * @param {string|null} p.homedir
81
+ * @param {string} p.cwd
82
+ * @param {string|null} [p.managedPath]
83
+ * @param {string|null} [p.cliSettings] --settings value: a path, or inline JSON
84
+ * @param {Record<string,string|undefined>} [p.env]
85
+ * @param {(file:string) => string} p.readFile
86
+ * @param {(file:string) => boolean} p.exists
87
+ * @returns {{layers: SettingsLayer[], errors: string[]}} layers in precedence order, HIGHEST first
88
+ */
89
+ export function loadSettingsLayers({ homedir, cwd, managedPath = null, cliSettings = null, env = {}, readFile, exists }) {
90
+ /** @type {SettingsLayer[]} */
91
+ const layers = [];
92
+ const errors = [];
93
+ const read = (/** @type {SettingsScope} */ scope, /** @type string */ file, /** @type string */ baseDir) => {
94
+ if (!exists(file)) return;
95
+ try {
96
+ const settings = JSON.parse(readFile(file));
97
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) throw new Error("not a JSON object");
98
+ layers.push({ scope, path: file, baseDir, settings });
99
+ } catch (e) {
100
+ errors.push(`${scope} settings ${file}: ${e instanceof Error ? e.message : e}`);
101
+ }
102
+ };
103
+ if (managedPath) read("managed", managedPath, "/");
104
+ if (cliSettings !== null && cliSettings !== undefined) {
105
+ const v = String(cliSettings).trim();
106
+ if (v.startsWith("{")) {
107
+ try {
108
+ layers.push({ scope: "cli", path: "--settings (inline)", baseDir: cwd, settings: JSON.parse(v) });
109
+ } catch (e) {
110
+ errors.push(`--settings: not valid JSON (${e instanceof Error ? e.message : e})`);
111
+ }
112
+ } else {
113
+ const file = path.resolve(cwd, v);
114
+ if (!exists(file)) errors.push(`--settings ${file}: file not found`);
115
+ else read("cli", file, cwd);
116
+ }
117
+ }
118
+ read("local", path.join(cwd, ".claude", "settings.local.json"), cwd);
119
+ read("project", path.join(cwd, ".claude", "settings.json"), cwd);
120
+ const userDir = userConfigDir({ homedir, cwd, env });
121
+ if (userDir) read("user", path.join(userDir, "settings.json"), homedir ?? userDir);
122
+ return { layers, errors };
123
+ }
124
+
125
+ /**
126
+ * @typedef {Object} HookEntry
127
+ * @property {string} matcher
128
+ * @property {Array<{type:string, command?:string, timeout?:number}>} hooks
129
+ * @property {string} source
130
+ * @property {string} baseDir
131
+ * @property {SettingsScope} [scope]
132
+ */
133
+
134
+ /** Layers whose word approves project MCP servers or switches off hooks. */
135
+ const APPROVING_SCOPES = new Set(["managed", "cli", "user"]);
136
+ const HOOK_SWITCH_SCOPES = new Set(["managed", "user"]);
137
+
138
+ /**
139
+ * @param {SettingsLayer[]} layers HIGHEST precedence first
140
+ * @param {{allowedTools?:string[], disallowedTools?:string[], permissionMode?:string|null, cwd:string}} flags
141
+ */
142
+ export function mergeSettings(layers, flags) {
143
+ /** Problems that would silently drop a restriction: the run must not start. */
144
+ const errors = [];
145
+ /** Problems that lose nothing protective (an unknown hook event name). */
146
+ const warnings = [];
147
+ const ruleSets = [];
148
+ const additionalDirectories = [];
149
+ /** @type {Record<string, HookEntry[]>} */
150
+ const hooks = Object.fromEntries(HOOK_EVENTS.map((e) => [e, []]));
151
+ const pluginDirs = [];
152
+ let defaultMode = null;
153
+ /** @type {string|null} */
154
+ let managedDefaultMode = null;
155
+ let disableBypass = false;
156
+ let disableAllHooks = null;
157
+ /** @type {{enableAll:boolean|null, enabled:string[], disabled:string[]}} */
158
+ const mcpApproval = { enableAll: null, enabled: [], disabled: [] };
159
+ const names = (/** @type unknown */ v) => (Array.isArray(v) ? v.map(String) : []);
160
+ /** @type {Record<string,string>} */
161
+ const env = {};
162
+ /** @type {string|null} */
163
+ let model = null;
164
+ /** @type {{contextWindows:Record<string,unknown>, compaction:Record<string,unknown>, promptCacheKey?:boolean, imageInput?:boolean, webTools?:boolean}} */
165
+ const engine = { contextWindows: {}, compaction: {} };
166
+
167
+ // Lowest precedence first, so a scalar set later wins.
168
+ for (const layer of [...layers].reverse()) {
169
+ const s = layer.settings || {};
170
+ const label = `${layer.scope} ${layer.path}`;
171
+ const perms = s.permissions;
172
+ if (perms !== undefined) {
173
+ if (!perms || typeof perms !== "object") errors.push(`${label}: "permissions" must be an object`);
174
+ else {
175
+ const compiled = compileRules(perms, { source: label, baseDir: layer.baseDir });
176
+ ruleSets.push(compiled.rules);
177
+ errors.push(...compiled.errors);
178
+ if (typeof perms.defaultMode === "string") {
179
+ defaultMode = perms.defaultMode;
180
+ if (layer.scope === "managed") managedDefaultMode = perms.defaultMode;
181
+ }
182
+ if (perms.disableBypassPermissionsMode === "disable") disableBypass = true;
183
+ if (Array.isArray(perms.additionalDirectories)) {
184
+ for (const d of perms.additionalDirectories) additionalDirectories.push(path.resolve(layer.baseDir, String(d)));
185
+ }
186
+ }
187
+ }
188
+ if (typeof s.disableAllHooks === "boolean") {
189
+ if (HOOK_SWITCH_SCOPES.has(layer.scope)) disableAllHooks = s.disableAllHooks;
190
+ else warnings.push(`${label}: disableAllHooks is honoured only in user or managed settings (ignored)`);
191
+ }
192
+ mcpApproval.disabled.push(...names(s.disabledMcpjsonServers));
193
+ if (s.enableAllProjectMcpServers !== undefined || s.enabledMcpjsonServers !== undefined) {
194
+ if (APPROVING_SCOPES.has(layer.scope)) {
195
+ if (typeof s.enableAllProjectMcpServers === "boolean") mcpApproval.enableAll = s.enableAllProjectMcpServers;
196
+ mcpApproval.enabled.push(...names(s.enabledMcpjsonServers));
197
+ } else {
198
+ warnings.push(`${label}: project MCP servers cannot be approved from ${layer.scope} settings (ignored); approve them in ~/.claude/settings.json or pass --mcp-config`);
199
+ }
200
+ }
201
+ if (s.hooks !== undefined) {
202
+ if (!s.hooks || typeof s.hooks !== "object") errors.push(`${label}: "hooks" must be an object`);
203
+ else {
204
+ for (const [event, entries] of Object.entries(s.hooks)) {
205
+ if (!HOOK_EVENTS.includes(event)) {
206
+ warnings.push(`${label}: unknown hook event "${event}" (ignored)`);
207
+ continue;
208
+ }
209
+ if (!Array.isArray(entries)) {
210
+ errors.push(`${label}: hooks.${event} must be an array`);
211
+ continue;
212
+ }
213
+ for (const entry of entries) {
214
+ if (!entry || !Array.isArray(entry.hooks)) {
215
+ errors.push(`${label}: hooks.${event} entries need a "hooks" array`);
216
+ continue;
217
+ }
218
+ hooks[event].push({ matcher: typeof entry.matcher === "string" ? entry.matcher : "", hooks: entry.hooks, source: label, baseDir: layer.baseDir, scope: layer.scope });
219
+ }
220
+ }
221
+ }
222
+ }
223
+ const dirs = s.cohort?.pluginDirs;
224
+ if (Array.isArray(dirs)) for (const d of dirs) pluginDirs.push({ dir: path.resolve(layer.baseDir, String(d)), source: label });
225
+ // CF-21: env, model, apiKeyHelper; the engine's own `cohort` keys.
226
+ if (s.env !== undefined) {
227
+ if (!s.env || typeof s.env !== "object" || Array.isArray(s.env)) warnings.push(`${label}: "env" must be an object (ignored)`);
228
+ else {
229
+ for (const [k, v] of Object.entries(s.env)) {
230
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") env[k] = String(v);
231
+ else warnings.push(`${label}: env.${k} must be a string (ignored)`);
232
+ }
233
+ }
234
+ }
235
+ if (typeof s.model === "string" && s.model.trim() !== "") model = s.model.trim();
236
+ if (s.apiKeyHelper !== undefined) warnings.push(`${label}: apiKeyHelper is ignored by the Cohort Engine; its gateway credential comes from COHORT_LLM_TOKEN`);
237
+ const c = s.cohort && typeof s.cohort === "object" ? s.cohort : {};
238
+ for (const key of /** @type {const} */ (["contextWindows", "compaction"])) {
239
+ if (c[key] && typeof c[key] === "object" && !Array.isArray(c[key])) Object.assign(engine[key], c[key]);
240
+ }
241
+ if (typeof c.promptCacheKey === "boolean") engine.promptCacheKey = c.promptCacheKey;
242
+ if (typeof c.imageInput === "boolean") engine.imageInput = c.imageInput;
243
+ // A restriction: `webTools: false` in any layer holds (a repository cannot switch back on what the seat switched off).
244
+ if (c.webTools === false) engine.webTools = false;
245
+ }
246
+
247
+ const cli = compileRules({ allow: flags.allowedTools ?? [], deny: flags.disallowedTools ?? [] }, { source: "command line", baseDir: flags.cwd });
248
+ errors.push(...cli.errors);
249
+ // Managed rules are part of `layers`; CLI rules add to them (rules only ever
250
+ // add restrictions or permissions, and deny/ask win by category).
251
+ ruleSets.push(cli.rules);
252
+
253
+ if (managedDefaultMode !== null && flags.permissionMode && flags.permissionMode !== managedDefaultMode) {
254
+ warnings.push(`managed settings set permissions.defaultMode "${managedDefaultMode}", which overrides the requested permission mode "${flags.permissionMode}"`);
255
+ }
256
+
257
+ return {
258
+ rules: mergeRuleSets(...ruleSets),
259
+ mode: managedDefaultMode ?? flags.permissionMode ?? defaultMode ?? "default",
260
+ defaultMode,
261
+ disableBypassPermissionsMode: disableBypass,
262
+ additionalDirectories,
263
+ // Switched off, only the managed layer's own hooks still run.
264
+ hooks: disableAllHooks === true ? Object.fromEntries(HOOK_EVENTS.map((e) => [e, hooks[e].filter((h) => h.scope === "managed")])) : hooks,
265
+ hooksDisabled: disableAllHooks === true,
266
+ mcpApproval,
267
+ pluginDirs,
268
+ env,
269
+ model,
270
+ engine,
271
+ errors,
272
+ warnings,
273
+ };
274
+ }