@geraldmaron/construct 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +55 -38
  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 +261 -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 +103 -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
@@ -0,0 +1,121 @@
1
+ /**
2
+ * lib/orchestration/host-sampling.mjs — Phase 2 of host-subscription execution:
3
+ * construct-mcp drives the awaiting-host loop itself via MCP sampling
4
+ * (`sampling/createMessage`), instead of leaving each materialized prompt for
5
+ * the calling agent to execute and submit back through orchestration_task_result
6
+ * (the Phase 1 pickup loop, lib/orchestration/runtime.mjs submitHostTaskResult).
7
+ *
8
+ * Both phases share the exact same materialization (worker.mjs
9
+ * materializeTaskPrompt) and the exact same recording path
10
+ * (submitHostTaskResult) — sampling only changes WHO executes each prompt: the
11
+ * client's own model via the MCP sampling capability, called by this server,
12
+ * rather than the calling agent's own reasoning turn. A sampling-executed
13
+ * task is still host-reported (provenanceSource 'host-reported'), since the
14
+ * client, not Construct, ran the model — Construct only relayed the call.
15
+ *
16
+ * Verified against the installed SDK (@modelcontextprotocol/sdk@1.29.0,
17
+ * package.json declares ^1.12.0): Server#createMessage and
18
+ * Server#getClientCapabilities both exist (node_modules/@modelcontextprotocol/
19
+ * sdk/dist/esm/server/index.js) and are the accessors this module depends on.
20
+ * If a future SDK removes or renames either, resolveHostExecutionMode still
21
+ * degrades to 'pickup' (Phase 1) rather than a hard failure — sampling is
22
+ * additive, pickup is the primary path by design (Claude Code's own sampling
23
+ * support is unconfirmed at the time of writing; VS Code is the surface
24
+ * expected to exercise this path).
25
+ */
26
+
27
+ import { submitHostTaskResult } from './runtime.mjs';
28
+
29
+ // A specialist answer is a single turn, same ceiling the provider worker uses
30
+ // for a non-reasoning call (worker.mjs MAX_OUTPUT_TOKENS) — kept independent
31
+ // here since a client's sampling budget is negotiated per-request, not a
32
+ // process-wide constant shared with the provider path.
33
+
34
+ export const SAMPLING_MAX_TOKENS = 2048;
35
+
36
+ /**
37
+ * Decide which loop drives an awaiting-host run's remaining tasks: the
38
+ * client-driven MCP sampling loop, or the Phase 1 pickup loop (the calling
39
+ * agent executes and submits manually). `auto` (the config default) prefers
40
+ * sampling only when the connected client actually declared the capability at
41
+ * initialize time; an explicit `sampling` request against a client that never
42
+ * declared it still falls back to pickup rather than issuing a request the
43
+ * client would reject.
44
+ *
45
+ * @param {object} opts
46
+ * @param {object} [opts.config] loaded project config (orchestration.hostExecution)
47
+ * @param {object} [opts.clientCapabilities] server.getClientCapabilities() result
48
+ * @returns {'sampling'|'pickup'}
49
+ */
50
+ export function resolveHostExecutionMode({ config, clientCapabilities } = {}) {
51
+ const configured = config?.orchestration?.hostExecution || 'auto';
52
+ const samplingDeclared = Boolean(clientCapabilities?.sampling);
53
+ if (configured === 'pickup') return 'pickup';
54
+ if (configured === 'sampling' || configured === 'auto') return samplingDeclared ? 'sampling' : 'pickup';
55
+ return 'pickup';
56
+ }
57
+
58
+ function extractSamplingText(sampled) {
59
+ const content = sampled?.content;
60
+ if (Array.isArray(content)) {
61
+ return content.filter((c) => c?.type === 'text').map((c) => c.text || '').join('');
62
+ }
63
+ if (content?.type === 'text') return content.text || '';
64
+ return '';
65
+ }
66
+
67
+ /**
68
+ * Drive every `awaiting-host` task on a run through the client's MCP sampling
69
+ * capability, recording each result through submitHostTaskResult (the same
70
+ * path the Phase 1 pickup loop uses) so the run finalizes through the one
71
+ * honest terminal-status computation regardless of which phase closed it out.
72
+ *
73
+ * A sampling call that throws (client rejects, network drop, malformed
74
+ * response) stops the loop early rather than fabricating a result — the
75
+ * remaining tasks stay `awaiting-host` exactly as Phase 1 leaves them, so the
76
+ * run is still honestly resumable by a manual pickup loop or a later retry,
77
+ * never silently marked done with invented output.
78
+ *
79
+ * @param {object} opts
80
+ * @param {object} opts.server the MCP Server instance (server.createMessage)
81
+ * @param {object} opts.run the awaiting-host run (or any run with awaiting-host tasks)
82
+ * @param {string} opts.cwd
83
+ * @param {Record<string,string>} [opts.env]
84
+ * @returns {Promise<object>} the run after every reachable task was sampled and recorded
85
+ */
86
+ export async function driveHostSamplingLoop({ server, run, cwd, env = process.env } = {}) {
87
+ let latestRun = run;
88
+ let task = (latestRun.tasks || []).find((t) => t.status === 'awaiting-host');
89
+
90
+ while (task) {
91
+ const { system = '', user = '' } = task.hostPrompt || {};
92
+ let sampled;
93
+ try {
94
+ sampled = await server.createMessage({
95
+ messages: [{ role: 'user', content: { type: 'text', text: user } }],
96
+ systemPrompt: system,
97
+ maxTokens: SAMPLING_MAX_TOKENS,
98
+ modelPreferences: { intelligencePriority: 0.6, speedPriority: 0.4, costPriority: 0.4 },
99
+ });
100
+ } catch (err) {
101
+ console.error(`[construct-mcp] sampling/createMessage failed for task ${task.id} on run ${latestRun.runId}: ${err.message}`);
102
+ break;
103
+ }
104
+
105
+ const output = extractSamplingText(sampled);
106
+ if (!output.trim()) {
107
+ console.error(`[construct-mcp] sampling/createMessage returned empty content for task ${task.id} on run ${latestRun.runId}; leaving it awaiting-host.`);
108
+ break;
109
+ }
110
+
111
+ const { run: updated, nextTask } = await submitHostTaskResult(
112
+ cwd, latestRun.runId, task.id,
113
+ { output, model: sampled.model || null, provider: 'mcp-sampling' },
114
+ { env },
115
+ );
116
+ latestRun = updated;
117
+ task = nextTask ? (updated.tasks || []).find((t) => t.id === nextTask.id) : null;
118
+ }
119
+
120
+ return latestRun;
121
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * lib/orchestration/policy-constants.mjs — shared enums for orchestration
3
+ * routing policy.
4
+ *
5
+ * Extracted from lib/orchestration-policy.mjs (construct-rf26.10) so
6
+ * classification.mjs, gates.mjs, and flow-selection.mjs can each import the
7
+ * enums without importing the whole policy module (avoids a cycle back
8
+ * through orchestration-policy.mjs's re-export layer). orchestration-policy.mjs
9
+ * re-exports these unchanged for existing callers.
10
+ */
11
+
12
+ export const EXECUTION_TRACKS = {
13
+ immediate: 'immediate',
14
+ focused: 'focused',
15
+ orchestrated: 'orchestrated',
16
+ };
17
+
18
+ export const INTENT_CLASSES = {
19
+ research: 'research',
20
+ implementation: 'implementation',
21
+ investigation: 'investigation',
22
+ evaluation: 'evaluation',
23
+ fix: 'fix',
24
+ };
25
+
26
+ // RFC-0004 §2: each intent class maps to the team that naturally owns that work.
27
+ // The primary team drives ownership in teamRouting; specialist selection stays
28
+ // flavor-driven. Domain refinements the RFC notes (investigation/fix can fall to
29
+ // operations-group, research to product-group) layer on as the decision matrix
30
+ // grows — the base mapping is deterministic so routing is testable.
31
+
32
+ export const INTENT_TO_TEAM = Object.freeze({
33
+ research: 'strategy-team',
34
+ implementation: 'engineering-team',
35
+ investigation: 'engineering-team',
36
+ evaluation: 'quality-team',
37
+ fix: 'engineering-team',
38
+ });
39
+
40
+ export const WORK_CATEGORIES = {
41
+ visual: 'visual',
42
+ deep: 'deep',
43
+ quick: 'quick',
44
+ writing: 'writing',
45
+ analysis: 'analysis',
46
+ };
47
+
48
+ export const TERMINAL_STATES = ['DONE', 'BLOCKED', 'NEEDS_MAIN_INPUT'];
@@ -0,0 +1,197 @@
1
+ /**
2
+ * lib/orchestration/provider-outcome.mjs — Typed provider outcome classification,
3
+ * bounded retry policy, and citation-grounding checks for the provider worker
4
+ * (construct-5wkl).
5
+ *
6
+ * A 2xx HTTP response is transport success, not task success: the body can
7
+ * still carry an empty answer, a content-policy refusal, or a reasoning-only
8
+ * response with nothing in the visible channel. This module turns both
9
+ * transport failures (rate limits, 5xx, timeouts) and content-shaped failures
10
+ * into the same stable, machine-readable code shape so
11
+ * lib/orchestration/worker.mjs can throw one error type that
12
+ * lib/orchestration/runtime.mjs's existing catch path already records as
13
+ * task.status='failed' with task.error.code — no runtime.mjs change needed.
14
+ */
15
+
16
+ export const PROVIDER_ERROR_CODES = Object.freeze({
17
+ RATE_LIMITED: 'PROVIDER_RATE_LIMITED',
18
+ SERVER_ERROR: 'PROVIDER_SERVER_ERROR',
19
+ TIMEOUT: 'PROVIDER_TIMEOUT',
20
+ AUTH_ERROR: 'PROVIDER_AUTH_ERROR',
21
+ NO_CREDITS: 'PROVIDER_NO_CREDITS',
22
+ CONTENT_FILTERED: 'PROVIDER_CONTENT_FILTERED',
23
+ EMPTY_CONTENT: 'PROVIDER_EMPTY_CONTENT',
24
+ REASONING_ONLY: 'PROVIDER_REASONING_ONLY',
25
+ MALFORMED_RESPONSE: 'PROVIDER_MALFORMED_RESPONSE',
26
+ EXECUTION_FAILED: 'PROVIDER_EXECUTION_FAILED',
27
+ });
28
+
29
+ // Only genuinely transient, infrastructure-level conditions are retryable —
30
+ // a content-policy refusal or a malformed body will not change on retry, and
31
+ // retrying an auth failure just spends another call to fail the same way.
32
+
33
+ const RETRYABLE_CODES = new Set([
34
+ PROVIDER_ERROR_CODES.RATE_LIMITED,
35
+ PROVIDER_ERROR_CODES.SERVER_ERROR,
36
+ PROVIDER_ERROR_CODES.TIMEOUT,
37
+ ]);
38
+
39
+ export function isRetryableCode(code) {
40
+ return RETRYABLE_CODES.has(code);
41
+ }
42
+
43
+ // HTTP-status classification. OpenRouter, OpenAI, and Anthropic all use 429
44
+ // for rate limiting and 5xx for upstream/provider errors; 401/403 are
45
+ // credential problems no retry can fix; 402 is the insufficient-credits
46
+ // status, which gets its own code so the remediation can steer the caller to
47
+ // the credit-free host backend instead of another spend path. Anything else
48
+ // keeps the prior generic code so existing callers/tests are unaffected.
49
+
50
+ export function classifyHttpFailure(status) {
51
+ if (status === 429) return { code: PROVIDER_ERROR_CODES.RATE_LIMITED, retryable: true };
52
+ if (status >= 500 && status < 600) return { code: PROVIDER_ERROR_CODES.SERVER_ERROR, retryable: true };
53
+ if (status === 401 || status === 403) return { code: PROVIDER_ERROR_CODES.AUTH_ERROR, retryable: false };
54
+ if (status === 402) return { code: PROVIDER_ERROR_CODES.NO_CREDITS, retryable: false };
55
+ return { code: PROVIDER_ERROR_CODES.EXECUTION_FAILED, retryable: false };
56
+ }
57
+
58
+ // AbortSignal.timeout firing surfaces as a DOMException/Error named
59
+ // TimeoutError or AbortError; timedFetch's own fallback throws a plain Error
60
+ // with "timed out" in the message when a stub fetchImpl ignores the signal.
61
+
62
+ export function isTimeoutError(err) {
63
+ if (!err) return false;
64
+ if (err.name === 'TimeoutError' || err.name === 'AbortError') return true;
65
+ return /timed out|ETIMEDOUT/i.test(String(err.message || ''));
66
+ }
67
+
68
+ // Normalizes an OpenAI/OpenRouter-shaped chat-completions body's first choice.
69
+ // OpenRouter additionally reports native_finish_reason (the upstream model's
70
+ // own value before OpenRouter's own normalization).
71
+
72
+ export function extractChoiceMeta(data) {
73
+ const choice = data?.choices?.[0];
74
+ return {
75
+ message: choice?.message && typeof choice.message === 'object' ? choice.message : null,
76
+ finishReason: choice?.finish_reason ?? null,
77
+ nativeFinishReason: choice?.native_finish_reason ?? null,
78
+ };
79
+ }
80
+
81
+ export function extractUsage(data) {
82
+ const usage = data?.usage;
83
+ if (!usage || typeof usage !== 'object') return null;
84
+ const promptTokens = Number(usage.prompt_tokens ?? usage.input_tokens);
85
+ const completionTokens = Number(usage.completion_tokens ?? usage.output_tokens);
86
+ const totalTokens = Number(usage.total_tokens ?? (promptTokens + completionTokens));
87
+ if (!Number.isFinite(promptTokens) && !Number.isFinite(completionTokens) && !Number.isFinite(totalTokens)) return null;
88
+ return {
89
+ promptTokens: Number.isFinite(promptTokens) ? promptTokens : undefined,
90
+ completionTokens: Number.isFinite(completionTokens) ? completionTokens : undefined,
91
+ totalTokens: Number.isFinite(totalTokens) ? totalTokens : undefined,
92
+ };
93
+ }
94
+
95
+ // After a 2xx response, transport succeeded but the answer may still be
96
+ // unusable. A content-policy refusal and a reasoning-only response (visible
97
+ // answer empty, model reasoning non-empty, finish_reason length — the model
98
+ // spent its entire output budget on reasoning tokens and never reached the
99
+ // answer) are both distinguishable from a genuinely empty response.
100
+
101
+ export function classifyContentOutcome({ content, finishReason, reasoning, wantsReasoning }) {
102
+ const text = typeof content === 'string' ? content.trim() : '';
103
+ const hasReasoning = typeof reasoning === 'string' && reasoning.trim().length > 0;
104
+
105
+ if (finishReason === 'content_filter') {
106
+ return { ok: false, code: PROVIDER_ERROR_CODES.CONTENT_FILTERED, retryable: false };
107
+ }
108
+ if (!text && wantsReasoning && hasReasoning) {
109
+ return { ok: false, code: PROVIDER_ERROR_CODES.REASONING_ONLY, retryable: false };
110
+ }
111
+ if (!text) {
112
+ return { ok: false, code: PROVIDER_ERROR_CODES.EMPTY_CONTENT, retryable: false };
113
+ }
114
+ return { ok: true };
115
+ }
116
+
117
+ // Malformed-choices guard: a 2xx body missing choices[0].message entirely is
118
+ // not the same failure as an empty string answer — it means the provider's
119
+ // response shape itself did not match what the caller expects.
120
+
121
+ export function isMalformedChoice(data) {
122
+ const choice = data?.choices?.[0];
123
+ return !choice || typeof choice !== 'object' || !choice.message || typeof choice.message !== 'object';
124
+ }
125
+
126
+ // Exponential backoff with a fixed small ceiling — provider calls already run
127
+ // seconds-to-minutes (PROVIDER_TIMEOUT_DEFAULT_MS), so the retry delay only
128
+ // needs to clear a rate-limit window's typical reset granularity, not model
129
+ // latency. attempt is 1-indexed (the delay before the 2nd, 3rd, ... call).
130
+
131
+ export function computeBackoffMs(attempt, baseMs = 250) {
132
+ return baseMs * 2 ** Math.max(0, attempt - 1);
133
+ }
134
+
135
+ async function defaultSleep(ms) {
136
+ return new Promise((r) => setTimeout(r, ms));
137
+ }
138
+
139
+ /**
140
+ * Retry wrapper: calls `attemptFn(attemptNumber)` up to `maxAttempts` times
141
+ * (1-indexed). `attemptFn` must throw an error carrying `.code` and
142
+ * `.retryable` (as produced by classifyHttpFailure/isTimeoutError callers) on
143
+ * failure. Retries only when `.retryable` is true and attempts remain;
144
+ * preserves the final error's full chain by attaching `.attempts` and
145
+ * `.retryCount` rather than replacing it, so the caller sees exactly what the
146
+ * last real attempt failed with (construct-5wkl AC#4).
147
+ */
148
+ export async function withProviderRetry(attemptFn, { maxAttempts = 3, baseDelayMs = 250, sleep = defaultSleep } = {}) {
149
+ let lastErr;
150
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
151
+ try {
152
+ const result = await attemptFn(attempt);
153
+ if (result && typeof result === 'object') result.retryCount = attempt - 1;
154
+ return result;
155
+ } catch (err) {
156
+ lastErr = err;
157
+ const canRetry = err?.retryable === true && attempt < maxAttempts;
158
+ if (!canRetry) {
159
+ if (err && typeof err === 'object') err.retryCount = attempt - 1;
160
+ throw err;
161
+ }
162
+ await sleep(computeBackoffMs(attempt, baseDelayMs));
163
+ }
164
+ }
165
+ throw lastErr;
166
+ }
167
+
168
+ // ── Evidence grounding (construct-5wkl AC#5) ─────────────────────────────────
169
+ // A web-capable specialist's governed evidence (task.webEvidence, ADR-0050) is
170
+ // the only citation source Construct has actually observed. Any URL the model
171
+ // writes into its answer that does not appear in that governed evidence is
172
+ // either fabricated outright or drawn from ungoverned model memory — either
173
+ // way it is not verified evidence and must not be presented as such.
174
+
175
+ const URL_PATTERN = /https?:\/\/[^\s)\]}"'<>]+/gi;
176
+
177
+ function normalizeUrl(url) {
178
+ return String(url || '').trim().replace(/[.,;:!?)\]}'"]+$/, '').replace(/\/$/, '').toLowerCase();
179
+ }
180
+
181
+ export function extractCitedUrls(text) {
182
+ if (typeof text !== 'string' || !text) return [];
183
+ const matches = text.match(URL_PATTERN) || [];
184
+ return [...new Set(matches.map(normalizeUrl))];
185
+ }
186
+
187
+ /**
188
+ * Returns the subset of URLs cited in `text` that do not appear among
189
+ * `evidenceUrls` (the governed web-evidence list). An empty return means every
190
+ * citation in the text traces to observed evidence.
191
+ */
192
+ export function findUnverifiedCitations(text, evidenceUrls = []) {
193
+ const cited = extractCitedUrls(text);
194
+ if (!cited.length) return [];
195
+ const known = new Set((evidenceUrls || []).map(normalizeUrl));
196
+ return cited.filter((url) => !known.has(url));
197
+ }
@@ -13,6 +13,25 @@ import { getInstalledVersion } from '../version.mjs';
13
13
  import { CONTRACT_VERSION, MIN_CLIENT_CONTRACT_VERSION } from '../embedded-contract/contract-version.mjs';
14
14
  import { getDeploymentMode } from '../deployment-mode.mjs';
15
15
  import { doctorRoot } from '../config/xdg.mjs';
16
+ import { resolveExecution } from '../embedded-contract/execution.mjs';
17
+ import { resolveEmbeddedModel } from '../embedded-contract/model-resolve.mjs';
18
+ import { resolveWorkerBackend } from './runtime.mjs';
19
+ import { resolveWebCapability } from './web-capability.mjs';
20
+ import { loadProjectConfig } from '../config/project-config.mjs';
21
+
22
+ // A run planned with no explicit backend only actually reaches the host worker
23
+ // backend when TWO things are true: the call is MCP-originated (so
24
+ // orchestration_run's own host-default applies at all — a bare CLI call keeps
25
+ // the inline default) and a real host session is attached to receive the
26
+ // materialized prompts and submit results back. `host-session` scope with a
27
+ // named host is the strongest signal readiness has for the second condition —
28
+ // a self-report or a bare local-probe/local-config check proves no live host is
29
+ // on the other end of this call, so host execution would only ever produce an
30
+ // abandoned awaiting-host run with nobody to submit results.
31
+
32
+ function hostExecutionViability({ observationScope, host }) {
33
+ return observationScope === 'host-session' && Boolean(host);
34
+ }
16
35
 
17
36
  export const ORCHESTRATION_READINESS_REASONS = Object.freeze([
18
37
  'attached',
@@ -23,6 +42,8 @@ export const ORCHESTRATION_READINESS_REASONS = Object.freeze([
23
42
  'capability_negotiation_failed',
24
43
  'version_mismatch',
25
44
  'tool_unlisted',
45
+ 'model_unresolved',
46
+ 'execution_degraded',
26
47
  'unknown',
27
48
  ]);
28
49
 
@@ -35,12 +56,14 @@ const NEXT_STEPS = Object.freeze({
35
56
  attached: 'Proceed: call orchestration_policy, then orchestration_run when policy indicates an orchestrated or focused run.',
36
57
  host_not_attached: 'Refresh the host adapter with `construct sync`, restart the host session, then rerun `construct orchestrate preflight --json` inside the target project.',
37
58
  server_unreachable: 'Run `construct doctor`, verify the generated MCP command points at this checkout, then rerun `construct sync` if the server path is stale.',
38
- auth_unavailable: 'Configure the required Construct/provider credential, restart the host so its MCP process inherits it, then rerun preflight.',
59
+ auth_unavailable: 'Set CONSTRUCT_ORCHESTRATION_TOKEN or CONSTRUCT_DASHBOARD_TOKEN so the MCP process serving CONSTRUCT_ORCHESTRATION_URL inherits it, restart the host, then rerun preflight.',
39
60
  profile_mismatch: 'Check the active Construct scope/profile and host adapter selection, then rerun `construct sync` from the project root.',
40
- capability_negotiation_failed: 'Restart the host session so MCP initialize/tools.list negotiation runs again; if it persists, attach the diagnostic bundle to GH #323.',
61
+ capability_negotiation_failed: 'Restart the host session so MCP initialize/tools.list negotiation runs again; if it persists, report the issue with the printed Diagnostic id at https://github.com/geraldmaron/construct/issues.',
41
62
  version_mismatch: 'Upgrade Construct and refresh adapters with `construct sync` so the host and server use compatible contract versions.',
42
63
  tool_unlisted: 'Refresh adapters with `construct sync`; if using a reduced local tool surface, ensure orchestration_run is reachable through the `call` gateway enum.',
43
- unknown: 'Run `construct doctor` and attach the diagnostic bundle to GH #323.',
64
+ model_unresolved: 'Set CX_MODEL_REASONING/STANDARD/FAST, or configure a provider credential (e.g. ANTHROPIC_API_KEY, OPENROUTER_API_KEY), then rerun preflight.',
65
+ execution_degraded: 'Run `construct orchestrate preflight --json` and inspect the modelResolved/workerBackend/webMode fields to see which one degrades this env; a same-family-fallback or config-error there predicts the same degradation orchestration_run would report.',
66
+ unknown: 'Run `construct doctor` and report the issue with the printed Diagnostic id at https://github.com/geraldmaron/construct/issues.',
44
67
  });
45
68
 
46
69
  function normalizeList(values) {
@@ -65,13 +88,20 @@ function hasTool(tool, observed, reachable) {
65
88
  return observed.includes(tool) || reachable.includes(tool);
66
89
  }
67
90
 
91
+ // An unresolved `op://vault/item/field` literal is a configured *reference*,
92
+ // not a materialized credential — counting it as a present key would tell the
93
+ // verdict chain a provider is usable when the secret was never actually read.
94
+ function isMaterializedSecret(value) {
95
+ return Boolean(value) && !/^op:\/\//.test(String(value).trim());
96
+ }
97
+
68
98
  function redactedEnvFacts(env) {
69
99
  return {
70
100
  hasRemoteOrchestrationUrl: Boolean(env.CONSTRUCT_ORCHESTRATION_URL),
71
101
  hasRemoteOrchestrationToken: Boolean(env.CONSTRUCT_ORCHESTRATION_TOKEN || env.CONSTRUCT_DASHBOARD_TOKEN),
72
- hasOpenRouterKey: Boolean(env.OPENROUTER_API_KEY),
73
- hasAnthropicKey: Boolean(env.ANTHROPIC_API_KEY),
74
- hasOpenAiKey: Boolean(env.OPENAI_API_KEY),
102
+ hasOpenRouterKey: isMaterializedSecret(env.OPENROUTER_API_KEY),
103
+ hasAnthropicKey: isMaterializedSecret(env.ANTHROPIC_API_KEY),
104
+ hasOpenAiKey: isMaterializedSecret(env.OPENAI_API_KEY),
75
105
  };
76
106
  }
77
107
 
@@ -81,12 +111,19 @@ export function buildOrchestrationReadiness(input = {}, { env = process.env, cwd
81
111
  const reachableTools = normalizeList(input.reachableTools ?? input.reachable_tools);
82
112
  const host = input.host ?? null;
83
113
  const sessionId = input.sessionId ?? input.session_id ?? null;
84
- const observationScope = input.observationScope ?? input.observation_scope ?? (observedTools.length || reachableTools.length ? 'host-session' : 'local-config');
114
+ const observationScope = input.observationScope ?? input.observation_scope ?? (observedTools.length || reachableTools.length ? 'local-probe' : 'local-config');
85
115
  const probeError = input.probeError ?? input.probe_error ?? null;
86
- const clientContractVersion = input.clientContractVersion ?? input.client_contract_version ?? CONTRACT_VERSION;
116
+ // No self-default to CONTRACT_VERSION: a caller that never reports its own
117
+ // contract version is unknown, not "assumed current" — assuming current
118
+ // made the version_mismatch check untrippable by construction.
119
+ const clientContractVersion = input.clientContractVersion ?? input.client_contract_version ?? null;
87
120
  const profileExpected = input.expectedProfile ?? input.expected_profile ?? null;
88
121
  const profileActual = input.actualProfile ?? input.actual_profile ?? null;
89
- const authRequired = Boolean(input.authRequired ?? input.auth_required);
122
+ const envFacts = redactedEnvFacts(env);
123
+ // A configured CONSTRUCT_ORCHESTRATION_URL means orchestration_run WILL hit
124
+ // a remote that needs a token; auth is required by that fact alone, not
125
+ // only when a caller opts in with --auth-required.
126
+ const authRequired = Boolean(input.authRequired ?? input.auth_required ?? envFacts.hasRemoteOrchestrationUrl);
90
127
 
91
128
  const missingTools = requiredTools.filter((tool) => !hasTool(tool, observedTools, reachableTools));
92
129
  const constructVersion = getInstalledVersion()?.version || 'unknown';
@@ -98,15 +135,17 @@ export function buildOrchestrationReadiness(input = {}, { env = process.env, cwd
98
135
  if (probeError) {
99
136
  reasonCode = 'server_unreachable';
100
137
  detail = String(probeError);
101
- } else if (compareSemver(clientContractVersion, MIN_CLIENT_CONTRACT_VERSION) < 0) {
138
+ } else if (clientContractVersion && compareSemver(clientContractVersion, MIN_CLIENT_CONTRACT_VERSION) < 0) {
102
139
  reasonCode = 'version_mismatch';
103
140
  detail = `Client contract ${clientContractVersion} is older than minimum ${MIN_CLIENT_CONTRACT_VERSION}.`;
104
141
  } else if (profileExpected && profileActual && profileExpected !== profileActual) {
105
142
  reasonCode = 'profile_mismatch';
106
143
  detail = `Expected profile ${profileExpected}, observed ${profileActual}.`;
107
- } else if (authRequired && !(env.CONSTRUCT_ORCHESTRATION_TOKEN || env.CONSTRUCT_DASHBOARD_TOKEN)) {
144
+ } else if (authRequired && !envFacts.hasRemoteOrchestrationToken) {
108
145
  reasonCode = 'auth_unavailable';
109
- detail = 'A remote/team orchestration token is required but not configured.';
146
+ detail = envFacts.hasRemoteOrchestrationUrl
147
+ ? 'CONSTRUCT_ORCHESTRATION_URL is configured but CONSTRUCT_ORCHESTRATION_TOKEN/CONSTRUCT_DASHBOARD_TOKEN is not set.'
148
+ : 'A remote/team orchestration token is required but not configured.';
110
149
  } else if (observationScope === 'host-session' && observedTools.length === 0 && reachableTools.length === 0) {
111
150
  reasonCode = 'host_not_attached';
112
151
  detail = 'No observed MCP tools were provided for the active host session.';
@@ -115,7 +154,55 @@ export function buildOrchestrationReadiness(input = {}, { env = process.env, cwd
115
154
  detail = `Missing required tool(s): ${missingTools.join(', ')}.`;
116
155
  }
117
156
 
157
+ // Attachment/version/auth/tool checks above answer liveness ("is the tool
158
+ // reachable?"). None of them predict whether orchestration_run on this same
159
+ // env would actually serve, so a green verdict here could still precede a
160
+ // "No model could be resolved" degradation. Resolve the identical serve
161
+ // path orchestration_run uses — model tiers, worker backend, web grant —
162
+ // env/config-local, no network calls, so a reason code already set above
163
+ // (a real liveness failure) is never overwritten by a serve-ability check.
164
+ const config = (() => { try { return loadProjectConfig(cwd, env).config || {}; } catch { return {}; } })();
165
+ const workerBackend = resolveWorkerBackend({ config });
166
+ const modelResolved = {};
167
+ let firstUnresolvedTier = null;
168
+ for (const tier of ['reasoning', 'standard', 'fast']) {
169
+ const resolved = resolveEmbeddedModel({ requestedTier: tier }, { env });
170
+ modelResolved[tier] = resolved.resolutionSource !== 'config-error';
171
+ if (!modelResolved[tier] && !firstUnresolvedTier) firstUnresolvedTier = tier;
172
+ }
173
+ const execData = resolveExecution({ requestedStrategy: 'orchestrated' }, { env, cwd });
174
+ const providerFamily = (execData.selectedProvider || '').replace(/^openrouter-.*/, 'openrouter');
175
+ // resolveWebCapability checks env.WEB_SEARCH_URL before ever looking at
176
+ // family, so a governed grant must not be skipped just because no LLM
177
+ // provider resolved — call it unconditionally and let it fall through to
178
+ // 'unavailable' on its own when neither WEB_SEARCH_URL nor family applies.
179
+ const webGrant = resolveWebCapability({ family: providerFamily, env });
180
+ const credentialMaterializable = execData.modelResolution?.requiresCredential === false || execData.selectedModel != null;
181
+
182
+ if (reasonCode === 'attached' && firstUnresolvedTier) {
183
+ reasonCode = 'model_unresolved';
184
+ const presentKeys = Object.entries({ anthropic: envFacts.hasAnthropicKey, openrouter: envFacts.hasOpenRouterKey, openai: envFacts.hasOpenAiKey })
185
+ .filter(([, present]) => present)
186
+ .map(([name]) => name);
187
+ const keyNote = presentKeys.length ? `Provider key(s) detected: ${presentKeys.join(', ')}.` : 'No provider key detected (ANTHROPIC_API_KEY, OPENROUTER_API_KEY, OPENAI_API_KEY).';
188
+ detail = `No model could be resolved for the ${firstUnresolvedTier} tier — orchestration_run would degrade on this env. ${execData.modelResolution?.error?.remediation || ''} ${keyNote}`.trim();
189
+ } else if (reasonCode === 'attached' && execData.degraded) {
190
+ reasonCode = 'execution_degraded';
191
+ detail = execData.degradationReason || 'The execution contract would degrade on this env.';
192
+ }
193
+
194
+ // A server-self-report catalog is a liveness-shaped fact ("the process
195
+ // exposes tools"), not a readiness-shaped one ("this session can serve
196
+ // orchestration_run") — disclose it on every verdict, pass or fail, rather
197
+ // than laundering it into an unqualified host-session pass. Applied last so
198
+ // a later-computed reasonCode/detail (model_unresolved, execution_degraded)
199
+ // never silently drops this caveat by overwriting `detail` above it.
200
+ if (observationScope === 'server-self-report') {
201
+ detail = `${detail} Derived from the server self-report catalog, not an observed host session; orchestration_run serve-ability for this session was not independently confirmed.`.trim();
202
+ }
203
+
118
204
  const verdict = reasonCode === 'attached' ? 'pass' : 'fail';
205
+ const hostExecutionViable = hostExecutionViability({ observationScope, host });
119
206
  const bundle = {
120
207
  diagnosticId,
121
208
  host,
@@ -133,7 +220,12 @@ export function buildOrchestrationReadiness(input = {}, { env = process.env, cwd
133
220
  missingTools,
134
221
  reasonCode,
135
222
  detail,
136
- env: redactedEnvFacts(env),
223
+ modelResolved,
224
+ credentialMaterializable,
225
+ workerBackend,
226
+ webMode: webGrant.mode,
227
+ hostExecutionViable,
228
+ env: envFacts,
137
229
  };
138
230
 
139
231
  return {
@@ -151,6 +243,15 @@ export function buildOrchestrationReadiness(input = {}, { env = process.env, cwd
151
243
  constructVersion,
152
244
  contractVersion: CONTRACT_VERSION,
153
245
  minClientContractVersion: MIN_CLIENT_CONTRACT_VERSION,
246
+ modelResolved,
247
+ credentialMaterializable,
248
+ workerBackend,
249
+ webMode: webGrant.mode,
250
+ // Whether a run planned with no explicit worker_backend on THIS session
251
+ // would both default to (host) and have a real host attached to execute it
252
+ // — distinct from workerBackend, which reports the config-resolved
253
+ // backend a bare CLI/no-session check would predict.
254
+ hostExecutionViable,
154
255
  diagnosticBundle: bundle,
155
256
  };
156
257
  }