@cohortapp/agent-sdk 2.16.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 (529) hide show
  1. package/.claude/settings.json +18 -0
  2. package/.env.example +23 -7
  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/poller-daemon-setup.md +4 -1
  9. package/docs/guides/setup-wizard.md +1 -1
  10. package/docs/runbooks/fleet-rollout.md +156 -0
  11. package/docs/runbooks/mac-mini-bootstrap.md +12 -14
  12. package/lib/action-executor.js +19 -3
  13. package/lib/budget-guard.mjs +279 -3
  14. package/lib/channels/base-adapter.mjs +3 -1
  15. package/lib/channels/contract.mjs +2 -1
  16. package/lib/channels/inbox-item.mjs +8 -0
  17. package/lib/claude-bin.mjs +5 -6
  18. package/lib/cli/doctor-checks.mjs +141 -10
  19. package/lib/cli/global-setup-extras.mjs +5 -1
  20. package/lib/cli/inbox.mjs +100 -15
  21. package/lib/cli/seat-auth.mjs +463 -0
  22. package/lib/cli/session.mjs +80 -12
  23. package/lib/collective/capture.mjs +8 -6
  24. package/lib/collective/global-config.mjs +63 -1
  25. package/lib/collective/presence.mjs +142 -5
  26. package/lib/comms/send-gate.mjs +559 -1
  27. package/lib/context/budget.mjs +327 -0
  28. package/lib/context/history-scope.mjs +138 -0
  29. package/lib/diagnostics/alerts.mjs +49 -0
  30. package/lib/diagnostics/cadence-output-freshness.mjs +288 -0
  31. package/lib/engine/agents/definitions.mjs +343 -0
  32. package/lib/engine/agents/persist.mjs +275 -0
  33. package/lib/engine/agents/runtime.mjs +748 -0
  34. package/lib/engine/agents/usage.mjs +95 -0
  35. package/lib/engine/auth-status.mjs +139 -0
  36. package/lib/engine/budget.mjs +194 -0
  37. package/lib/engine/cli.mjs +1204 -0
  38. package/lib/engine/commands/index.mjs +269 -0
  39. package/lib/engine/context/budget.mjs +219 -0
  40. package/lib/engine/context/cache.mjs +125 -0
  41. package/lib/engine/context/child-env.mjs +215 -0
  42. package/lib/engine/context/compaction.mjs +342 -0
  43. package/lib/engine/context/images.mjs +90 -0
  44. package/lib/engine/context/instructions.mjs +327 -0
  45. package/lib/engine/context/lazy-instructions.mjs +169 -0
  46. package/lib/engine/context/manager.mjs +182 -0
  47. package/lib/engine/context/real-path.mjs +91 -0
  48. package/lib/engine/context/secret-values.mjs +163 -0
  49. package/lib/engine/context/settings.mjs +274 -0
  50. package/lib/engine/context/stream-input.mjs +159 -0
  51. package/lib/engine/guard.mjs +152 -0
  52. package/lib/engine/hooks.mjs +713 -0
  53. package/lib/engine/loop.mjs +560 -0
  54. package/lib/engine/mcp/client.mjs +254 -0
  55. package/lib/engine/mcp/config.mjs +301 -0
  56. package/lib/engine/mcp/http.mjs +201 -0
  57. package/lib/engine/mcp/index.mjs +146 -0
  58. package/lib/engine/mcp/jsonrpc.mjs +147 -0
  59. package/lib/engine/mcp/naming.mjs +66 -0
  60. package/lib/engine/mcp/resources.mjs +89 -0
  61. package/lib/engine/mcp/results.mjs +133 -0
  62. package/lib/engine/mcp/stdio.mjs +137 -0
  63. package/lib/engine/mcp/supervisor.mjs +116 -0
  64. package/lib/engine/messages.mjs +104 -0
  65. package/lib/engine/output/json.mjs +164 -0
  66. package/lib/engine/output/stream-json.mjs +266 -0
  67. package/lib/engine/permissions.mjs +845 -0
  68. package/lib/engine/process-identity.mjs +164 -0
  69. package/lib/engine/process-tree.mjs +551 -0
  70. package/lib/engine/prompt.mjs +60 -0
  71. package/lib/engine/session/store.mjs +299 -0
  72. package/lib/engine/session-runtime/args.mjs +97 -0
  73. package/lib/engine/session-runtime/host.mjs +143 -0
  74. package/lib/engine/session-runtime/inbox.mjs +122 -0
  75. package/lib/engine/session-runtime/notifications.mjs +129 -0
  76. package/lib/engine/session-runtime/registry.mjs +328 -0
  77. package/lib/engine/session-runtime/runner.mjs +344 -0
  78. package/lib/engine/session-runtime/socket.mjs +212 -0
  79. package/lib/engine/session-runtime/wakeup.mjs +115 -0
  80. package/lib/engine/skills/index.mjs +321 -0
  81. package/lib/engine/tools/bash-background.mjs +533 -0
  82. package/lib/engine/tools/bash.mjs +216 -0
  83. package/lib/engine/tools/edit.mjs +97 -0
  84. package/lib/engine/tools/glob.mjs +81 -0
  85. package/lib/engine/tools/grep.mjs +224 -0
  86. package/lib/engine/tools/index.mjs +84 -0
  87. package/lib/engine/tools/list-agents.mjs +32 -0
  88. package/lib/engine/tools/ls.mjs +127 -0
  89. package/lib/engine/tools/monitor.mjs +82 -0
  90. package/lib/engine/tools/notebook-edit.mjs +218 -0
  91. package/lib/engine/tools/read.mjs +103 -0
  92. package/lib/engine/tools/schedule-wakeup.mjs +45 -0
  93. package/lib/engine/tools/schema.mjs +144 -0
  94. package/lib/engine/tools/send-message.mjs +77 -0
  95. package/lib/engine/tools/session.mjs +70 -0
  96. package/lib/engine/tools/todo.mjs +144 -0
  97. package/lib/engine/tools/toolsearch.mjs +217 -0
  98. package/lib/engine/tools/walk.mjs +193 -0
  99. package/lib/engine/tools/web-switch.mjs +31 -0
  100. package/lib/engine/tools/webfetch-html.mjs +387 -0
  101. package/lib/engine/tools/webfetch-net.mjs +340 -0
  102. package/lib/engine/tools/webfetch.mjs +198 -0
  103. package/lib/engine/tools/websearch.mjs +91 -0
  104. package/lib/engine/tools/workflow.mjs +95 -0
  105. package/lib/engine/tools/write.mjs +76 -0
  106. package/lib/engine/tui/line-editor.mjs +137 -0
  107. package/lib/engine/tui/render.mjs +86 -0
  108. package/lib/engine/tui/tui.mjs +274 -0
  109. package/lib/engine/wire/anthropic-messages.mjs +263 -0
  110. package/lib/engine/wire/effort.mjs +36 -0
  111. package/lib/engine/wire/errors.mjs +496 -0
  112. package/lib/engine/wire/http.mjs +441 -0
  113. package/lib/engine/wire/index.mjs +76 -0
  114. package/lib/engine/wire/openai-chat.mjs +332 -0
  115. package/lib/engine/wire/prompt-cache.mjs +79 -0
  116. package/lib/engine/wire/search.mjs +140 -0
  117. package/lib/engine/wire/sse.mjs +114 -0
  118. package/lib/engine/wire/stall.mjs +349 -0
  119. package/lib/engine/wire/token-provider.mjs +175 -0
  120. package/lib/engine/wire/usage.mjs +192 -0
  121. package/lib/engine/workflow/host.mjs +524 -0
  122. package/lib/engine/workflow/journal.mjs +188 -0
  123. package/lib/engine/workflow/json-schema.mjs +171 -0
  124. package/lib/engine/workflow/meta.mjs +329 -0
  125. package/lib/engine/workflow/notifications.mjs +52 -0
  126. package/lib/engine/workflow/runtime.mjs +447 -0
  127. package/lib/engine/workflow/sandbox.mjs +534 -0
  128. package/lib/engine/workflow/worker.mjs +141 -0
  129. package/lib/engine/workflow/worktree.mjs +74 -0
  130. package/lib/execution/disposition.mjs +1 -1
  131. package/lib/execution/intake.mjs +10 -0
  132. package/lib/execution/surface-policy.mjs +15 -0
  133. package/lib/learning/curator.mjs +8 -6
  134. package/lib/learning/reflect.mjs +8 -6
  135. package/lib/model-router/catalog/cohort.yaml +137 -0
  136. package/lib/model-router/catalog.mjs +118 -1
  137. package/lib/model-router/economics.mjs +9 -0
  138. package/lib/model-router/failover.mjs +67 -16
  139. package/lib/model-router/llm-task.mjs +39 -3
  140. package/lib/model-router/resolve.mjs +95 -3
  141. package/lib/model-router/spawn.mjs +46 -47
  142. package/lib/model-router/taxonomy.mjs +126 -4
  143. package/lib/org/cost-sync.mjs +141 -11
  144. package/lib/org/inbound/broadcast.mjs +289 -0
  145. package/lib/org/inbound/collective.mjs +375 -0
  146. package/lib/org/inbound/directedness.mjs +96 -8
  147. package/lib/org/inbound/facts.mjs +82 -4
  148. package/lib/org/inbound/hydrate.mjs +555 -51
  149. package/lib/org/inbound/project.mjs +22 -0
  150. package/lib/org/inbound/surfaces.mjs +14 -0
  151. package/lib/org/llm-token.mjs +879 -0
  152. package/lib/org/mesh.mjs +61 -0
  153. package/lib/org/messaging.mjs +3 -1
  154. package/lib/org/protocol.checksum +1 -1
  155. package/lib/org/protocol.mjs +15 -0
  156. package/lib/org/quota.mjs +520 -0
  157. package/lib/org/tool-surface.mjs +104 -16
  158. package/lib/org/ui-parity.mjs +16 -1
  159. package/lib/org/work-ledger.mjs +37 -6
  160. package/lib/rate-guard.mjs +114 -1
  161. package/lib/resource-governor.mjs +41 -6
  162. package/lib/runtime/adapter.mjs +823 -0
  163. package/lib/runtime/child-env.mjs +191 -0
  164. package/lib/runtime/legacy-shell-guard.mjs +97 -0
  165. package/lib/runtime/seat-engine.mjs +162 -0
  166. package/lib/session/ask-ledger.mjs +271 -0
  167. package/lib/session/current-work.mjs +676 -0
  168. package/lib/session/feed-core.mjs +40 -3
  169. package/lib/session/launch-args.mjs +56 -4
  170. package/lib/session/status-summary.mjs +26 -9
  171. package/lib/session/upgrade-notice.mjs +42 -0
  172. package/lib/setup/claude-probe.mjs +117 -13
  173. package/lib/setup/enrich.mjs +13 -10
  174. package/lib/setup/sections/model.mjs +39 -13
  175. package/lib/telemetry/collect.mjs +208 -9
  176. package/lib/upgrade/ignored-drift.mjs +105 -0
  177. package/lib/voice/post-call-brief.mjs +30 -17
  178. package/package.json +15 -3
  179. package/plugins/maestro-skills/skills/board-work.md +5 -0
  180. package/plugins/maestro-skills/skills/inbound-triage.md +56 -15
  181. package/plugins/maestro-skills/skills/main-session.md +18 -7
  182. package/scripts/ci/check-tarball-fidelity.mjs +126 -2
  183. package/scripts/ci/run-tests.mjs +47 -19
  184. package/scripts/cohort-llm/api-key-helper.mjs +92 -0
  185. package/scripts/collective/hook-runner.mjs +29 -2
  186. package/scripts/continuous-monitor.sh +13 -0
  187. package/scripts/cost/track-claude-usage.mjs +15 -0
  188. package/scripts/daemon/agent-daemon.mjs +408 -20
  189. package/scripts/daemon/assurance.mjs +48 -12
  190. package/scripts/daemon/cadence-consumer.mjs +218 -68
  191. package/scripts/daemon/cadence-handlers.mjs +73 -4
  192. package/scripts/daemon/classifier.mjs +75 -26
  193. package/scripts/daemon/context-compiler.mjs +104 -59
  194. package/scripts/daemon/deliver.mjs +30 -1
  195. package/scripts/daemon/dispatcher.mjs +804 -157
  196. package/scripts/daemon/health.mjs +14 -1
  197. package/scripts/daemon/lib/session-router.mjs +310 -42
  198. package/scripts/daemon/maestro-daemon.mjs +11 -0
  199. package/scripts/daemon/prompt-builder.mjs +121 -12
  200. package/scripts/daemon/responder.mjs +315 -146
  201. package/scripts/daemon/sdk-version.mjs +98 -16
  202. package/scripts/eval/probe-gateway.mjs +635 -0
  203. package/scripts/eval/replay/extract.mjs +270 -0
  204. package/scripts/eval/replay/grade.mjs +260 -0
  205. package/scripts/eval/replay/lib/config.mjs +50 -0
  206. package/scripts/eval/replay/lib/effects.mjs +65 -0
  207. package/scripts/eval/replay/lib/fixture.mjs +188 -0
  208. package/scripts/eval/replay/lib/judge.mjs +72 -0
  209. package/scripts/eval/replay/lib/redact.mjs +136 -0
  210. package/scripts/eval/replay/lib/sandbox.mjs +170 -0
  211. package/scripts/eval/replay/lib/schema-check.mjs +63 -0
  212. package/scripts/eval/replay/lib/transcript.mjs +76 -0
  213. package/scripts/eval/replay/mcp-replay-stub.mjs +101 -0
  214. package/scripts/eval/replay/report.mjs +185 -0
  215. package/scripts/eval/replay/run.mjs +404 -0
  216. package/scripts/fleet/rollout.mjs +1094 -0
  217. package/scripts/hooks/pre-send-audit.sh +36 -245
  218. package/scripts/hooks/pre-write-yaml-validate.mjs +275 -0
  219. package/scripts/hooks/validate-state-yaml.sh +190 -0
  220. package/scripts/huddle/huddle-llm.mjs +361 -0
  221. package/scripts/huddle/huddle-server.mjs +46 -121
  222. package/scripts/local-triggers/autoupdate.sh +448 -78
  223. package/scripts/local-triggers/run-trigger.sh +13 -0
  224. package/scripts/maintenance/pin-integrity.mjs +364 -0
  225. package/scripts/poll-slack-events.sh +41 -9
  226. package/scripts/poller/slack-socket-mode.mjs +28 -3
  227. package/scripts/session/supervisor.mjs +80 -13
  228. package/scripts/spawn-session.sh +13 -0
  229. package/bin/maestro.test.mjs +0 -1574
  230. package/lib/action-executor.test.mjs +0 -871
  231. package/lib/archetype.test.mjs +0 -132
  232. package/lib/assurance/plan-note.test.mjs +0 -234
  233. package/lib/assurance/room-budget.test.mjs +0 -486
  234. package/lib/assurance/tier.test.mjs +0 -174
  235. package/lib/autonomy.test.mjs +0 -66
  236. package/lib/backlog.test.mjs +0 -302
  237. package/lib/backup/policy.test.mjs +0 -305
  238. package/lib/budget-escalate.test.mjs +0 -232
  239. package/lib/budget-guard.envelope.test.mjs +0 -476
  240. package/lib/budget-guard.test.mjs +0 -427
  241. package/lib/cadence-bus-requeue.test.mjs +0 -83
  242. package/lib/cadence-bus-schedule.test.mjs +0 -194
  243. package/lib/cadence-bus.test.mjs +0 -720
  244. package/lib/cadences.test.mjs +0 -230
  245. package/lib/capability/inventory.test.mjs +0 -232
  246. package/lib/capability.test.mjs +0 -78
  247. package/lib/channels/base-adapter.test.mjs +0 -590
  248. package/lib/channels/channels.test.mjs +0 -371
  249. package/lib/channels/contract.test.mjs +0 -162
  250. package/lib/channels/inbox-item.test.mjs +0 -368
  251. package/lib/channels/orgmail/adapter.test.mjs +0 -448
  252. package/lib/channels/pairing.test.mjs +0 -270
  253. package/lib/channels/repeat-suppressor.test.mjs +0 -134
  254. package/lib/channels/slack-adapter.test.mjs +0 -212
  255. package/lib/channels/telegram-adapter.test.mjs +0 -306
  256. package/lib/channels/voice/adapter.test.mjs +0 -278
  257. package/lib/channels/whatsapp/adapter-baileys.test.mjs +0 -359
  258. package/lib/channels/whatsapp/baileys-typing.test.mjs +0 -154
  259. package/lib/charter.test.mjs +0 -89
  260. package/lib/claude-bin.test.mjs +0 -131
  261. package/lib/cli/board.test.mjs +0 -227
  262. package/lib/cli/design.test.mjs +0 -270
  263. package/lib/cli/doctor-checks.test.mjs +0 -336
  264. package/lib/cli/global-setup-extras.test.mjs +0 -462
  265. package/lib/cli/inbox.test.mjs +0 -230
  266. package/lib/cli/session-ack.test.mjs +0 -63
  267. package/lib/cli/session.test.mjs +0 -613
  268. package/lib/collective/capture.test.mjs +0 -121
  269. package/lib/collective/cards.test.mjs +0 -114
  270. package/lib/collective/config.test.mjs +0 -123
  271. package/lib/collective/global-config.test.mjs +0 -220
  272. package/lib/collective/global-skills.test.mjs +0 -126
  273. package/lib/collective/presence.test.mjs +0 -95
  274. package/lib/collective/recall.test.mjs +0 -116
  275. package/lib/collective/vendor-skills.test.mjs +0 -306
  276. package/lib/comms/send-gate.test.mjs +0 -770
  277. package/lib/comms.test.mjs +0 -41
  278. package/lib/cost/ledger-row.test.mjs +0 -183
  279. package/lib/design/design-md.test.mjs +0 -318
  280. package/lib/design/fixtures/DESIGN.golden.md +0 -238
  281. package/lib/design/fixtures/PRODUCT.golden.md +0 -67
  282. package/lib/design/fixtures/foundation.json +0 -133
  283. package/lib/design/refresh-gate.test.mjs +0 -144
  284. package/lib/design/write.test.mjs +0 -241
  285. package/lib/diagnostics/alerts.test.mjs +0 -318
  286. package/lib/diagnostics/backup-freshness.test.mjs +0 -185
  287. package/lib/diagnostics/counters.test.mjs +0 -206
  288. package/lib/diagnostics/events.test.mjs +0 -290
  289. package/lib/diagnostics/otel.test.mjs +0 -196
  290. package/lib/diagnostics/trace.test.mjs +0 -251
  291. package/lib/env-compat.test.mjs +0 -104
  292. package/lib/execution/disposition.test.mjs +0 -553
  293. package/lib/execution/drive.test.mjs +0 -270
  294. package/lib/execution/effects.test.mjs +0 -344
  295. package/lib/execution/intake.test.mjs +0 -389
  296. package/lib/execution/journal.test.mjs +0 -261
  297. package/lib/execution/match.test.mjs +0 -235
  298. package/lib/execution/pipeline.test.mjs +0 -392
  299. package/lib/execution/route.test.mjs +0 -186
  300. package/lib/execution/surface-policy.test.mjs +0 -162
  301. package/lib/fs-atomic.test.mjs +0 -72
  302. package/lib/fs-ownership.test.mjs +0 -158
  303. package/lib/goals/admission.test.mjs +0 -164
  304. package/lib/goals/classify.test.mjs +0 -167
  305. package/lib/goals/collaborate.test.mjs +0 -336
  306. package/lib/goals/gaps.test.mjs +0 -284
  307. package/lib/goals/loop.test.mjs +0 -845
  308. package/lib/hooks/bus.test.mjs +0 -387
  309. package/lib/identity/persona.test.mjs +0 -142
  310. package/lib/kpi-sensors.test.mjs +0 -278
  311. package/lib/kpi.test.mjs +0 -244
  312. package/lib/learning/config.test.mjs +0 -75
  313. package/lib/learning/counters.test.mjs +0 -69
  314. package/lib/learning/curator-consolidate.test.mjs +0 -238
  315. package/lib/learning/curator.test.mjs +0 -106
  316. package/lib/learning/reflect.test.mjs +0 -0
  317. package/lib/learning/session-index.test.mjs +0 -125
  318. package/lib/learning/skill-writer.test.mjs +0 -210
  319. package/lib/mandate/audit.test.mjs +0 -195
  320. package/lib/mandate/contract.test.mjs +0 -185
  321. package/lib/mandate/derive.test.mjs +0 -274
  322. package/lib/mandate/model.test.mjs +0 -164
  323. package/lib/mandate/refresh.test.mjs +0 -389
  324. package/lib/mcp/server.test.mjs +0 -426
  325. package/lib/model-router/auth-profiles.test.mjs +0 -580
  326. package/lib/model-router/catalog.test.mjs +0 -385
  327. package/lib/model-router/economics.test.mjs +0 -438
  328. package/lib/model-router/failover.test.mjs +0 -439
  329. package/lib/model-router/health.test.mjs +0 -338
  330. package/lib/model-router/integration-coverage.test.mjs +0 -831
  331. package/lib/model-router/integration.test.mjs +0 -564
  332. package/lib/model-router/ledger.test.mjs +0 -415
  333. package/lib/model-router/llm-task.test.mjs +0 -392
  334. package/lib/model-router/org-credentials.test.mjs +0 -265
  335. package/lib/model-router/pricing-refresh.test.mjs +0 -286
  336. package/lib/model-router/reconcile.test.mjs +0 -316
  337. package/lib/model-router/repair.test.mjs +0 -180
  338. package/lib/model-router/spawn.test.mjs +0 -446
  339. package/lib/model-router/taxonomy.test.mjs +0 -410
  340. package/lib/model-router.test.mjs +0 -1207
  341. package/lib/org/activity.test.mjs +0 -134
  342. package/lib/org/approvals.test.mjs +0 -216
  343. package/lib/org/awareness.test.mjs +0 -159
  344. package/lib/org/board-mine-cache.test.mjs +0 -53
  345. package/lib/org/board.test.mjs +0 -187
  346. package/lib/org/bootstrap-context.test.mjs +0 -153
  347. package/lib/org/client.test.mjs +0 -1206
  348. package/lib/org/cohort-client.test.mjs +0 -126
  349. package/lib/org/cost-sync.test.mjs +0 -153
  350. package/lib/org/doctor.test.mjs +0 -346
  351. package/lib/org/engagement-ledger.test.mjs +0 -112
  352. package/lib/org/engagement.test.mjs +0 -739
  353. package/lib/org/handoff.test.mjs +0 -269
  354. package/lib/org/inbound/directedness.test.mjs +0 -668
  355. package/lib/org/inbound/facts.test.mjs +0 -471
  356. package/lib/org/inbound/hydrate.test.mjs +0 -453
  357. package/lib/org/inbound/index.test.mjs +0 -429
  358. package/lib/org/inbound/project.test.mjs +0 -287
  359. package/lib/org/integration-tools.test.mjs +0 -160
  360. package/lib/org/keys.test.mjs +0 -92
  361. package/lib/org/knowledge.test.mjs +0 -326
  362. package/lib/org/leases.test.mjs +0 -235
  363. package/lib/org/mesh-directives.test.mjs +0 -110
  364. package/lib/org/mesh-integration.test.mjs +0 -127
  365. package/lib/org/mesh.test.mjs +0 -400
  366. package/lib/org/messaging.test.mjs +0 -471
  367. package/lib/org/param-contract.test.mjs +0 -477
  368. package/lib/org/policy.test.mjs +0 -237
  369. package/lib/org/protocol.checksum.test.mjs +0 -90
  370. package/lib/org/protocol.test.mjs +0 -323
  371. package/lib/org/push.test.mjs +0 -792
  372. package/lib/org/registry.test.mjs +0 -100
  373. package/lib/org/resource-tools.test.mjs +0 -361
  374. package/lib/org/tool-access.test.mjs +0 -144
  375. package/lib/org/tool-surface-integration.test.mjs +0 -120
  376. package/lib/org/tool-surface.test.mjs +0 -1268
  377. package/lib/org/typing.test.mjs +0 -291
  378. package/lib/org/ui-parity.test.mjs +0 -560
  379. package/lib/org/verify.test.mjs +0 -194
  380. package/lib/org/work-ledger.test.mjs +0 -273
  381. package/lib/plan/adoption-e2e.test.mjs +0 -366
  382. package/lib/plan/budget-enforcement.test.mjs +0 -400
  383. package/lib/plan/compile.test.mjs +0 -382
  384. package/lib/plan/emit.test.mjs +0 -269
  385. package/lib/plan/explain.test.mjs +0 -188
  386. package/lib/prompts/parallelism.test.mjs +0 -177
  387. package/lib/rag/rag.test.mjs +0 -505
  388. package/lib/rate-guard.test.mjs +0 -272
  389. package/lib/reactive-gate.test.mjs +0 -57
  390. package/lib/render.test.mjs +0 -68
  391. package/lib/resource-governor.test.mjs +0 -488
  392. package/lib/scheduling/dynamic-jobs.test.mjs +0 -344
  393. package/lib/scheduling/jitter.test.mjs +0 -140
  394. package/lib/secrets/broker.test.mjs +0 -280
  395. package/lib/secrets/providers.test.mjs +0 -274
  396. package/lib/security/audit-engine.test.mjs +0 -424
  397. package/lib/security/coerce-args.test.mjs +0 -281
  398. package/lib/security/dangerous-tools.test.mjs +0 -68
  399. package/lib/security/external-content.test.mjs +0 -84
  400. package/lib/security/redact.test.mjs +0 -441
  401. package/lib/security/secret-equal.test.mjs +0 -55
  402. package/lib/session/config.test.mjs +0 -92
  403. package/lib/session/feed-core.test.mjs +0 -198
  404. package/lib/session/first-run.test.mjs +0 -121
  405. package/lib/session/frontdoor.test.mjs +0 -205
  406. package/lib/session/handoffs.test.mjs +0 -183
  407. package/lib/session/identity.test.mjs +0 -180
  408. package/lib/session/inbox-claims.test.mjs +0 -286
  409. package/lib/session/launch-args.test.mjs +0 -157
  410. package/lib/session/liveness.test.mjs +0 -100
  411. package/lib/session/status-summary.test.mjs +0 -118
  412. package/lib/session-permissions.test.mjs +0 -120
  413. package/lib/setup/claude-probe.test.mjs +0 -187
  414. package/lib/setup/completeness.test.mjs +0 -110
  415. package/lib/setup/context-pack.test.mjs +0 -89
  416. package/lib/setup/enrich.test.mjs +0 -115
  417. package/lib/setup/enroll-from-cohort.test.mjs +0 -300
  418. package/lib/setup/integration.test.mjs +0 -162
  419. package/lib/setup/io.test.mjs +0 -77
  420. package/lib/setup/runner.test.mjs +0 -132
  421. package/lib/setup/sections/identity.test.mjs +0 -234
  422. package/lib/setup/sections/inventory.test.mjs +0 -198
  423. package/lib/setup/sections/learning.test.mjs +0 -81
  424. package/lib/setup/sections/mandate.test.mjs +0 -388
  425. package/lib/setup/sections/messaging.test.mjs +0 -127
  426. package/lib/setup/sections/model.test.mjs +0 -240
  427. package/lib/setup/sections/org.test.mjs +0 -346
  428. package/lib/setup/sections/orgmail.test.mjs +0 -118
  429. package/lib/setup/sections/recovery.test.mjs +0 -98
  430. package/lib/setup/sections/subagents.test.mjs +0 -429
  431. package/lib/setup/sections/verify.test.mjs +0 -175
  432. package/lib/setup/sot.test.mjs +0 -81
  433. package/lib/setup/state.test.mjs +0 -115
  434. package/lib/singleton.test.mjs +0 -151
  435. package/lib/subagents/cli.test.mjs +0 -389
  436. package/lib/subagents/client.test.mjs +0 -309
  437. package/lib/subagents/gap.test.mjs +0 -234
  438. package/lib/subagents/lock.test.mjs +0 -248
  439. package/lib/subagents/manifest.test.mjs +0 -175
  440. package/lib/subagents/refs.test.mjs +0 -204
  441. package/lib/subagents/resolve.test.mjs +0 -422
  442. package/lib/subagents/schema.test.mjs +0 -328
  443. package/lib/telemetry/alerts.test.mjs +0 -109
  444. package/lib/telemetry/collect.test.mjs +0 -1274
  445. package/lib/tool-definitions-integration.test.mjs +0 -83
  446. package/lib/tool-definitions.test.mjs +0 -437
  447. package/lib/upgrade/global-refresh.test.mjs +0 -65
  448. package/lib/upgrade/launchd-reconcile.test.mjs +0 -272
  449. package/lib/upgrade/post-steps.test.mjs +0 -200
  450. package/lib/upgrade/verify.test.mjs +0 -164
  451. package/lib/util/fetch-timeout.test.mjs +0 -202
  452. package/lib/util/reconnect.test.mjs +0 -369
  453. package/lib/util/unhandled.test.mjs +0 -216
  454. package/lib/voice/outbound.test.mjs +0 -69
  455. package/lib/voice/session-rotation.test.mjs +0 -114
  456. package/lib/voice/stt.test.mjs +0 -226
  457. package/lib/voice/voice.test.mjs +0 -990
  458. package/scripts/cadence/enqueue-cadence-tick.test.mjs +0 -187
  459. package/scripts/ci/check-docs-accuracy.test.mjs +0 -409
  460. package/scripts/ci/check-durable-write-seam.test.mjs +0 -90
  461. package/scripts/ci/check-no-build-artifacts.test.mjs +0 -71
  462. package/scripts/ci/check-no-residual-identity.test.mjs +0 -202
  463. package/scripts/ci/check-skill-packs.test.mjs +0 -495
  464. package/scripts/ci/check-subagent-frontmatter.test.mjs +0 -124
  465. package/scripts/ci/check.test.mjs +0 -194
  466. package/scripts/ci/conformance-org-api.test.mjs +0 -425
  467. package/scripts/cloud-relay/voice/relay-identity.test.mjs +0 -96
  468. package/scripts/collective/hook-runner.test.mjs +0 -173
  469. package/scripts/cost/fleet-digest.test.mjs +0 -207
  470. package/scripts/cost/track-claude-usage-pricing.test.mjs +0 -183
  471. package/scripts/cost/track-claude-usage.test.mjs +0 -148
  472. package/scripts/daemon/agent-daemon-board-mine.test.mjs +0 -96
  473. package/scripts/daemon/agent-daemon-design.test.mjs +0 -238
  474. package/scripts/daemon/agent-daemon-frontdoor.test.mjs +0 -60
  475. package/scripts/daemon/agent-daemon.test.mjs +0 -995
  476. package/scripts/daemon/assurance-e2e.test.mjs +0 -613
  477. package/scripts/daemon/assurance.test.mjs +0 -1791
  478. package/scripts/daemon/board-mirror.test.mjs +0 -165
  479. package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +0 -393
  480. package/scripts/daemon/cadence-consumer-governance.test.mjs +0 -276
  481. package/scripts/daemon/cadence-consumer.test.mjs +0 -776
  482. package/scripts/daemon/cadence-handlers.test.mjs +0 -837
  483. package/scripts/daemon/classifier-identity.test.mjs +0 -137
  484. package/scripts/daemon/classifier.test.mjs +0 -266
  485. package/scripts/daemon/classify-kind.test.mjs +0 -40
  486. package/scripts/daemon/context-compiler.test.mjs +0 -300
  487. package/scripts/daemon/deliver.test.mjs +0 -564
  488. package/scripts/daemon/dispatcher-cooldown.test.mjs +0 -122
  489. package/scripts/daemon/dispatcher-governance.test.mjs +0 -1013
  490. package/scripts/daemon/dispatcher-resume.test.mjs +0 -166
  491. package/scripts/daemon/execution-ladder.test.mjs +0 -470
  492. package/scripts/daemon/goal-steward-cadence.test.mjs +0 -312
  493. package/scripts/daemon/inbox-deferral-session.test.mjs +0 -49
  494. package/scripts/daemon/inbox-deferral.test.mjs +0 -336
  495. package/scripts/daemon/inbox-wake.test.mjs +0 -199
  496. package/scripts/daemon/integration.test.mjs +0 -149
  497. package/scripts/daemon/lib/self-echo.test.mjs +0 -153
  498. package/scripts/daemon/lib/session-router.test.mjs +0 -295
  499. package/scripts/daemon/prompt-builder-preamble.test.mjs +0 -210
  500. package/scripts/daemon/prompt-builder.test.mjs +0 -344
  501. package/scripts/daemon/responder-cost.test.mjs +0 -68
  502. package/scripts/daemon/responder-history.test.mjs +0 -185
  503. package/scripts/daemon/sdk-version.test.mjs +0 -31
  504. package/scripts/daemon/session-lock.test.mjs +0 -252
  505. package/scripts/daemon/session-outcomes.test.mjs +0 -533
  506. package/scripts/daemon/typing-registry.test.mjs +0 -102
  507. package/scripts/hooks/pre-send-audit.test.mjs +0 -354
  508. package/scripts/huddle/huddle-prompt.test.mjs +0 -176
  509. package/scripts/local-triggers/autoupdate.test.mjs +0 -518
  510. package/scripts/local-triggers/generate-plists.test.mjs +0 -456
  511. package/scripts/media-generation/brand-clause.test.mjs +0 -135
  512. package/scripts/org/send-orgmail.first-contact.test.mjs +0 -102
  513. package/scripts/poller/inbox-privilege-injection.test.mjs +0 -167
  514. package/scripts/poller/inbox-scan-poller.test.mjs +0 -295
  515. package/scripts/poller/lib/cloud-relay-dedup.test.mjs +0 -133
  516. package/scripts/poller/slack-socket-mode.test.mjs +0 -805
  517. package/scripts/poller-launchd/install.test.mjs +0 -243
  518. package/scripts/restore-from-backup.test.mjs +0 -181
  519. package/scripts/session/feed.test.mjs +0 -196
  520. package/scripts/session/supervisor-sh.test.mjs +0 -218
  521. package/scripts/session/supervisor.test.mjs +0 -482
  522. package/scripts/setup/configure-macos.test.mjs +0 -306
  523. package/scripts/setup/gen-subagent-manifest.test.mjs +0 -124
  524. package/scripts/setup/generate-agent-package-json.test.mjs +0 -143
  525. package/scripts/setup/generate-capability.test.mjs +0 -134
  526. package/scripts/setup/init-agent.test.mjs +0 -370
  527. package/scripts/setup/init-skill-marketplace.test.mjs +0 -193
  528. package/scripts/vendor/sync-skill-packs.test.mjs +0 -103
  529. 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
+ }