@geraldmaron/construct 1.4.2 → 1.5.1

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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -77,11 +77,17 @@ try {
77
77
  const cxDir = doctorRoot();
78
78
  mkdirSync(cxDir, { recursive: true });
79
79
 
80
- // Update last-agent files for coordination (shared + per-agent)
81
- const agentEntry = { agent: agentName, coordination: 'tracker-plus-plan', ts: new Date().toISOString(), outcome, description: description.slice(0, 200) };
80
+ // Update last-agent files for coordination (shared + per-agent). agentId is
81
+ // best-effort (null on hosts that don't populate it on this event) it lets
82
+ // guard-bash's role-fence check prove that a LATER command's own agent_id
83
+ // is the same subagent this dispatch was for, rather than fencing anyone
84
+ // riding a fresh timestamp (construct-7164).
85
+ const agentEntry = { agent: agentName, agentId: input?.agent_id ?? null, coordination: 'tracker-plus-plan', ts: new Date().toISOString(), outcome, description: description.slice(0, 200) };
82
86
  writeFileSync(join(cxDir, 'last-agent.json'), JSON.stringify(agentEntry));
83
- // Per-agent file prevents one agent dispatch from resetting another's fence window
84
- const safeName = agentName.replace(/[^a-z0-9._-]/gi, '_');
87
+ // Per-agent file prevents one agent dispatch from resetting another's fence
88
+ // window. Keyed by persona id with the cx- prefix stripped, matching
89
+ // guard-bash's CONSTRUCT_AGENT_ID lookup (construct-diq1).
90
+ const safeName = agentName.replace(/^cx-/, '').replace(/[^a-z0-9._-]/gi, '_');
85
91
  writeFileSync(join(cxDir, `last-agent-${safeName}.json`), JSON.stringify(agentEntry));
86
92
 
87
93
  // Per-project agent log (was ~/.cx/agent-log.jsonl which mixed every
@@ -41,8 +41,8 @@ const cwd = input?.cwd || process.cwd();
41
41
  function absOf(p) { return p.startsWith('/') ? p : resolve(cwd, p); }
42
42
 
43
43
  // Role-fence check: if the most recently dispatched persona is one we manage
44
- // (cx-sre, cx-qa, cx-security, cx-docs-keeper) and the dispatch is fresh,
45
- // emit an advisory stderr warning when the edit target is outside the fence.
44
+ // (cx-operations, cx-qa, cx-security) and the dispatch is fresh, emit an
45
+ // advisory stderr warning when the edit target is outside the fence.
46
46
  // Hard-block only when the fence returns "outside-fence" (default-deny case);
47
47
  // "needs-approval" routes to existing commit-approval flow.
48
48
 
@@ -54,7 +54,7 @@ if ((toolName === 'Edit' || toolName === 'Write' || toolName === 'MultiEdit') &&
54
54
  const lastTs = last?.ts ? Date.parse(last.ts) : 0;
55
55
  const fresh = lastTs && (Date.now() - lastTs) < 10 * 60 * 1000;
56
56
  const id = String(last?.agent || '').replace(/^cx-/, '');
57
- const ONBOARDED = new Set(['sre', 'qa', 'security', 'docs-keeper']);
57
+ const ONBOARDED = new Set(['operations', 'qa', 'security']);
58
58
  const targetPath = ti.file_path || ti.path || '';
59
59
  if (fresh && ONBOARDED.has(id) && targetPath) {
60
60
  const { checkAction } = await import('../roles/fence.mjs');
@@ -41,13 +41,13 @@ if (!CODE_RE.test(rel)) process.exit(0);
41
41
  const stale = checkGraphStaleness(cwd);
42
42
  if (stale.stale) {
43
43
  process.stderr.write(
44
- `[graph-impact] dependency graph stale (${stale.staleReason}). Run \`construct graph build\` before trusting impact output.\n`,
44
+ `[graph-impact] dependency graph stale (${stale.staleReason}). Run \`construct matrix build\` before trusting impact output.\n`,
45
45
  );
46
46
  }
47
47
 
48
48
  const impact = computeImpact({ rootDir: cwd, changedFiles: [rel] });
49
49
  if (!impact.graphPresent) {
50
- process.stderr.write('[graph-impact] no .cx/graph/ present — run `construct graph build` to enable test-impact advisories.\n');
50
+ process.stderr.write('[graph-impact] no .cx/graph/ present — run `construct matrix build` to enable test-impact advisories.\n');
51
51
  process.exit(0);
52
52
  }
53
53
 
@@ -57,9 +57,15 @@ const WARN_PATTERNS = [
57
57
  },
58
58
  ];
59
59
 
60
- async function getCommand() {
60
+ // agent_id is Claude Code's own subagent marker: present only when this
61
+ // invocation is running inside a subagent's own tool call, absent for
62
+ // main-thread (orchestrator) calls. It is the actor-identity signal the
63
+ // role-fence check below uses to prove WHO is issuing THIS command, instead
64
+ // of trusting how recently some persona was dispatched elsewhere (construct-7164).
65
+
66
+ async function getInvocation() {
61
67
  const fromEnv = process.env.TOOL_INPUT_COMMAND;
62
- if (fromEnv) return fromEnv;
68
+ if (fromEnv) return { command: fromEnv, agentId: null };
63
69
 
64
70
  return new Promise((resolve) => {
65
71
  let data = '';
@@ -68,16 +74,19 @@ async function getCommand() {
68
74
  rl.on('close', () => {
69
75
  try {
70
76
  const parsed = JSON.parse(data);
71
- resolve(parsed?.tool_input?.command ?? parsed?.command ?? '');
77
+ resolve({
78
+ command: parsed?.tool_input?.command ?? parsed?.command ?? '',
79
+ agentId: parsed?.agent_id ?? null,
80
+ });
72
81
  } catch (err) {
73
82
  logHookFailure({ hook: 'guard-bash', err, phase: 'parse' });
74
- resolve('');
83
+ resolve({ command: '', agentId: null });
75
84
  }
76
85
  });
77
86
  });
78
87
  }
79
88
 
80
- const command = await getCommand();
89
+ const { command, agentId: currentAgentId } = await getInvocation();
81
90
 
82
91
  if (!command) process.exit(0);
83
92
 
@@ -94,10 +103,21 @@ for (const { pattern, reason } of WARN_PATTERNS) {
94
103
  }
95
104
  }
96
105
 
97
- // Role-fence check: if the most recently dispatched persona is one we manage
98
- // and the dispatch is fresh, evaluate the command against the persona's
99
- // fence. git commit/push are always commit/push actions; bd ... maps to
100
- // allowedCommands prefix-match; everything else is the bash action.
106
+ // Role-fence check: evaluate the command against a persona's fence, but only
107
+ // when the CURRENT invocation's actor identity is verifiably that persona
108
+ // recency of a past dispatch is not proof of who is issuing THIS command
109
+ // (construct-7164: an orchestrator's or a different subagent's own bash
110
+ // calls were getting fenced by a persona dispatched minutes earlier
111
+ // elsewhere). Two identity signals are trusted: CONSTRUCT_AGENT_ID, an
112
+ // explicit self-declaration some hosts set before invoking commands as a
113
+ // given persona; and agent_id, Claude Code's own per-subagent marker, which
114
+ // is guaranteed absent for main-thread calls. The shared last-agent.json
115
+ // (no identity attached) can only corroborate a dispatch already proven by
116
+ // agent_id — it is never sufficient on its own. git commit/push are always
117
+ // commit/push actions; bd ... maps to allowedCommands prefix-match;
118
+ // everything else is the bash action.
119
+
120
+ const FENCE_WINDOW_MS = 5 * 60 * 1000;
101
121
 
102
122
  if (process.env.CONSTRUCT_ROLES !== 'off') {
103
123
  try {
@@ -105,24 +125,30 @@ if (process.env.CONSTRUCT_ROLES !== 'off') {
105
125
  const id = String(process.env.CONSTRUCT_AGENT_ID || '').replace(/^cx-/, '');
106
126
  let agentData = null;
107
127
 
108
- // Prefer per-agent timestamp file (prevents cross-agent fence window reset)
128
+ // Self-declared identity: the per-agent file is keyed by the same id the
129
+ // current invocation is claiming, so this match is identity-comparable
130
+ // by construction.
109
131
  if (id) {
110
132
  const perAgentPath = join(cxDir, `last-agent-${id.replace(/[^a-z0-9._-]/gi, '_')}.json`);
111
133
  if (existsSync(perAgentPath)) {
112
134
  agentData = JSON.parse(readFileSync(perAgentPath, 'utf8'));
113
135
  }
114
136
  }
115
- // Fall back to shared last-agent.json
116
- if (!agentData) {
137
+ // No self-declared id: only trust the shared dispatch record when the
138
+ // CURRENT invocation's own agent_id matches the one recorded at dispatch
139
+ // time, proving this is the SAME subagent call rather than a different
140
+ // actor riding a fresh timestamp.
141
+ if (!agentData && currentAgentId) {
117
142
  const sharedPath = join(cxDir, 'last-agent.json');
118
143
  if (existsSync(sharedPath)) {
119
- agentData = JSON.parse(readFileSync(sharedPath, 'utf8'));
144
+ const shared = JSON.parse(readFileSync(sharedPath, 'utf8'));
145
+ if (shared?.agentId && shared.agentId === currentAgentId) agentData = shared;
120
146
  }
121
147
  }
122
148
 
123
149
  if (agentData) {
124
150
  const lastTs = agentData?.ts ? Date.parse(agentData.ts) : 0;
125
- const fresh = lastTs && (Date.now() - lastTs) < 10 * 60 * 1000;
151
+ const fresh = lastTs && (Date.now() - lastTs) < FENCE_WINDOW_MS;
126
152
  const personaId = id || String(agentData?.agent || '').replace(/^cx-/, '');
127
153
  if (fresh && personaId) {
128
154
  const { isOnboarded } = await import('../roles/manifest.mjs');
@@ -145,7 +171,7 @@ if (process.env.CONSTRUCT_ROLES !== 'off') {
145
171
  `[fence] cx-${personaId} cannot run this command — outside declared fence.\n` +
146
172
  `Command: ${trimmed.slice(0, 200)}\n` +
147
173
  `Allowed commands: ${allowedList}\n` +
148
- `To bypass: CONSTRUCT_ROLES=off <command>\n`
174
+ `To bypass: set CONSTRUCT_ROLES=off as a real environment variable before starting this session (this hook runs as a separate process, so an inline "CONSTRUCT_ROLES=off <command>" prefix on the blocked command never reaches it).\n`
149
175
  );
150
176
  process.exit(2);
151
177
  }
@@ -2,14 +2,14 @@
2
2
  /**
3
3
  * lib/hooks/mcp-health-check.mjs — MCP health check hook — verifies MCP servers are reachable before tool use.
4
4
  *
5
- * Runs as PreToolUse on MCP tool calls. Checks that the target MCP server is running and reachable. Emits a warning (does not block) if the server is unavailable.
5
+ * Runs as PreToolUse on MCP tool calls. Checks that the target MCP server is running and reachable. Emits a warning (does not block) if the server is unavailable. A successful tool use resets the failure count for that server.
6
6
  *
7
7
  * @p95ms 51
8
8
  * @maxBlockingScope none (PreToolUse, warn-only)
9
9
  *
10
10
  * @lifecycle PreToolUse
11
11
  * @matcher mcp__.*
12
- * @exits 0 = pass | 2 = block tool call
12
+ * @exits 0 = pass (health check is warn-only; never blocks) | 2 = blocked by role fence (unrelated to health, see below)
13
13
  */
14
14
  import { readFileSync, writeFileSync, existsSync } from 'fs';
15
15
  import { homedir } from 'os';
@@ -46,12 +46,19 @@ if (markFailure) {
46
46
  process.exit(0);
47
47
  }
48
48
 
49
+ // Warn-only per @maxBlockingScope none: a tool-level failure is not proof the
50
+ // server is down, so this notice never blocks. Successful use (below) resets
51
+ // the counter, so the notice only appears while failures are still recent.
52
+
49
53
  const entry = cache[serverSlug];
50
54
  if (entry?.status === 'unhealthy' && (Date.now() - entry.since) < CACHE_TTL_MS) {
51
55
  process.stderr.write(
52
- `[mcp-health] The ${serverSlug} connection is unavailable (failed ${entry.failures} time${entry.failures !== 1 ? 's' : ''} recently). Skipping this step.\n`
56
+ `[mcp-health] The ${serverSlug} connection failed ${entry.failures} time${entry.failures !== 1 ? 's' : ''} recently.\n`
53
57
  );
54
- process.exit(2);
58
+ } else if (entry) {
59
+ entry.status = 'healthy';
60
+ entry.failures = 0;
61
+ try { writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2)); } catch { /* best effort */ }
55
62
  }
56
63
 
57
64
  // Role-fence check for MCP tool calls. Most personas have no MCP fence
@@ -20,22 +20,41 @@
20
20
  * @matcher *
21
21
  * @exits 0 = pass
22
22
  */
23
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
23
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
24
24
  import { execFileSync } from 'node:child_process';
25
25
  import { homedir } from 'node:os';
26
26
  import { join } from 'node:path';
27
27
  import {
28
28
  applyToEnv,
29
29
  classifyProviderFailure,
30
+ defaultModelRegistryPath,
30
31
  readOpenRouterApiKeyFromOpenCodeConfig,
31
32
  selectFallbackModel,
32
33
  writeProviderCooldown,
33
34
  } from '../model-router.mjs';
35
+ import { extractOpRef, resolveOpRef, resolveSecret } from '../providers/secret-resolver.mjs';
34
36
  import { readHookInput } from './_lib/input.mjs';
35
37
  import { doctorRoot } from '../config/xdg.mjs';
36
38
 
37
39
  const cooldownPath = join(doctorRoot(), 'provider-cooldowns.json');
38
40
  const envPath = join(process.cwd(), '.env');
41
+ const toolkitDir = process.env.CX_TOOLKIT_DIR || join(homedir(), '.construct');
42
+
43
+ // The fallback candidate chain must be loaded and bound into scope before
44
+ // selectFallbackModel runs, or its tier definition is always empty and a
45
+ // fallback can never fire — same registry file resolveModelTiers reads
46
+ // (specialists/org/models.json under the toolkit dir).
47
+
48
+ function readRegistryModels(env) {
49
+ try {
50
+ const registryPath = defaultModelRegistryPath(env);
51
+ if (!existsSync(registryPath)) return {};
52
+ const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
53
+ return registry?.models ?? {};
54
+ } catch {
55
+ return {};
56
+ }
57
+ }
39
58
 
40
59
  const input = readHookInput();
41
60
 
@@ -45,7 +64,8 @@ if (!classified || !classified.retryable) process.exit(0);
45
64
  process.stderr.write(`[model-fallback] ${classified.kind} detected (provider: ${classified.provider ?? 'unknown'}).\n`);
46
65
 
47
66
  const now = Date.now();
48
- const result = selectFallbackModel({ hookInput: input, envPath, cooldownPath, now });
67
+ const registryModels = readRegistryModels(process.env);
68
+ const result = selectFallbackModel({ hookInput: input, envPath, cooldownPath, registryModels, now });
49
69
 
50
70
  if (result) {
51
71
  process.stderr.write(`[model-fallback] Switching ${result.tier} → ${result.targetModel} (reason: ${result.reason}).\n`);
@@ -55,20 +75,44 @@ if (result) {
55
75
  }
56
76
 
57
77
  // No candidate resolved — fall back to full `construct models --apply` if possible.
58
- const toolkitDir = process.env.CX_TOOLKIT_DIR || join(homedir(), '.construct');
78
+ // No fallback was applied here, so no cooldown is written: writing one on a
79
+ // no-op path would suppress the provider on the NEXT failure even though this
80
+ // hook run changed nothing (construct-uccl.3).
59
81
  const constructBin = join(toolkitDir, 'bin', 'construct');
60
82
 
61
83
  if (!existsSync(constructBin)) {
62
84
  process.stderr.write(`[model-fallback] No fallback candidate and construct binary not found at ${constructBin}.\n`);
63
- if (classified.provider) writeProviderCooldown(cooldownPath, classified.provider, now);
64
85
  process.exit(0);
65
86
  }
66
87
 
67
- const openRouterApiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
88
+ function materializeConfiguredKey(rawValue) {
89
+ if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
90
+ const ref = extractOpRef(rawValue);
91
+ try {
92
+ return ref ? resolveOpRef(ref) : rawValue;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ function resolveOpenRouterApiKey() {
99
+ try {
100
+ const envKey = resolveSecret('OPENROUTER_API_KEY', { env: process.env });
101
+ if (envKey) return envKey;
102
+ } catch {
103
+ // Fall through to the OpenCode provider config.
104
+ }
105
+ try {
106
+ return materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+
112
+ const openRouterApiKey = resolveOpenRouterApiKey();
68
113
 
69
114
  if (!openRouterApiKey) {
70
115
  process.stderr.write('[model-fallback] No fallback candidate and no OpenRouter API key — cannot poll models.\n');
71
- if (classified.provider) writeProviderCooldown(cooldownPath, classified.provider, now);
72
116
  process.exit(0);
73
117
  }
74
118
 
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * The orchestration_policy tool classifies a request into immediate / focused /
6
6
  * orchestrated. An orchestrated verdict means the orchestrator must dispatch the
7
- * specialist chain (cx-architect, cx-security, cx-devil-advocate, …) before
7
+ * specialist chain (cx-architect, cx-security, cx-reviewer, …) before
8
8
  * authoring the deliverable — not write it solo. The persona instructs this, but
9
9
  * instruction is not enforcement: this hook is the Claude-only barrier.
10
10
  *
@@ -87,10 +87,21 @@ if (event === 'PostToolUse' && isPolicyTool(toolName)) {
87
87
  process.exit(0);
88
88
  }
89
89
 
90
+ // A dispatch tool arms `dispatched:true` unless its own response proves the
91
+ // dispatch produced nothing to synthesize (degraded run, zero tasks, or an
92
+ // error/failed status). Task dispatches carry no envelope — an empty/absent
93
+ // blob matches none of these markers, so they still clear the guard.
94
+
90
95
  if (event === 'PostToolUse' && isDispatchTool(toolName)) {
91
96
  if (!hasCx(cwd)) process.exit(0);
92
97
  const state = readState(cwd);
93
- if (state?.pending) writeState(cwd, { ...state, dispatched: true });
98
+ if (state?.pending) {
99
+ const blob = JSON.stringify(input?.tool_response ?? '').replace(/\\/g, '');
100
+ const failed = /"degraded"\s*:\s*true/.test(blob)
101
+ || /"tasks"\s*:\s*\[\s*\]/.test(blob)
102
+ || /"status"\s*:\s*"(error|failed)"/.test(blob);
103
+ if (!failed) writeState(cwd, { ...state, dispatched: true });
104
+ }
94
105
  process.exit(0);
95
106
  }
96
107
 
@@ -117,7 +128,7 @@ if (event === 'PreToolUse' && /^(Write|Edit|MultiEdit)$/.test(toolName)) {
117
128
  + 'but no specialist dispatch (orchestration_run / Task) has run yet.\n'
118
129
  + 'You may not author the deliverable solo. Emit the task-packet and dispatch the chain '
119
130
  + '(e.g. cx-architect owns structure, cx-security challenges the credential/state model, '
120
- + 'cx-devil-advocate pressure-tests the approach), then synthesize their verdicts in Construct\'s voice.\n'
131
+ + 'cx-reviewer pressure-tests the approach), then synthesize their verdicts in Construct\'s voice.\n'
121
132
  + 'If this write IS post-dispatch synthesis, run the dispatch tool first so the guard clears.\n',
122
133
  );
123
134
  process.exit(2);