@geraldmaron/construct 1.1.1 → 1.2.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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +258 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -18,6 +18,7 @@
18
18
  import { resolveContractChain } from './specialist-contracts.mjs';
19
19
  import { verifyRoute } from './intent-classifier.mjs';
20
20
  import { ownerForEvent, ownerForDoc, evaluateWatchConditions, knownDocTypes } from './orchestration/routing-tables.mjs';
21
+ import { getArtifactEntry } from './artifact-manifest.mjs';
21
22
 
22
23
  export const EXECUTION_TRACKS = {
23
24
  immediate: 'immediate',
@@ -653,6 +654,19 @@ export function formatOverlayTrace(roleFlavors = {}, request = '') {
653
654
  return `Overlays: ${parts.join(', ')}`;
654
655
  }
655
656
 
657
+ function resolveArtifactReviewRequirements(docAuthoring) {
658
+ if (!docAuthoring?.docType) {
659
+ return { requiredReviewers: [], optionalReviewers: [], releaseGate: null };
660
+ }
661
+ const entry = getArtifactEntry(docAuthoring.docType);
662
+ const gate = entry?.releaseGate ?? {};
663
+ return {
664
+ requiredReviewers: gate.requiredReviewers ?? [],
665
+ optionalReviewers: gate.optionalReviewers ?? [],
666
+ releaseGate: gate,
667
+ };
668
+ }
669
+
656
670
  export function routeRequest(options = {}) {
657
671
  const intent = classifyIntent(options.request);
658
672
  const riskFlags = detectRiskFlags(options.request);
@@ -679,6 +693,11 @@ export function routeRequest(options = {}) {
679
693
  if (framingChallenge.required && !specialists.includes('cx-devil-advocate')) {
680
694
  specialists = ['cx-devil-advocate', ...specialists];
681
695
  }
696
+
697
+ const artifactReview = resolveArtifactReviewRequirements(docAuthoring);
698
+ for (const reviewer of artifactReview.requiredReviewers) {
699
+ if (!specialists.includes(reviewer)) specialists = [...specialists, reviewer];
700
+ }
682
701
  // Pre-dispatch specialists that surface concerns before architecture locks in.
683
702
  // Order matters: business framing → R&D validation → legal/compliance review,
684
703
  // each prepended so it runs earliest in the chain.
@@ -715,6 +734,7 @@ export function routeRequest(options = {}) {
715
734
  framingChallenge,
716
735
  externalResearch,
717
736
  docAuthoring,
737
+ artifactReview,
718
738
  });
719
739
 
720
740
  const route = {
@@ -731,6 +751,7 @@ export function routeRequest(options = {}) {
731
751
  docAuthoring,
732
752
  externalResearch,
733
753
  framingChallenge,
754
+ artifactReview,
734
755
  contractChain,
735
756
  dispatchPlan: buildDispatchPlan({ track, intent, specialists }),
736
757
  dispatchSummary: buildDispatchSummary({ specialists, reasons }),
@@ -754,3 +775,32 @@ export function routeRequestVerified(options = {}) {
754
775
  const route = routeRequest(options);
755
776
  return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
756
777
  }
778
+
779
+ /**
780
+ * Build the construct→orchestrator contract packet (specialists/contracts.json).
781
+ * Callers must pass this object to agent_contract — not a bare goal string.
782
+ */
783
+ export function buildConstructToOrchestratorPacket(options = {}) {
784
+ const request = String(options.request || '');
785
+ const route = options.route || routeRequest({
786
+ request,
787
+ fileCount: options.fileCount ?? 0,
788
+ moduleCount: options.moduleCount ?? 0,
789
+ introducesContract: options.introducesContract ?? false,
790
+ explicitDrive: options.explicitDrive ?? false,
791
+ });
792
+ if (route.track === EXECUTION_TRACKS.immediate) return null;
793
+
794
+ const goal = String(options.goal || request || '').trim() || 'User-requested work';
795
+ const acceptanceCriteria = Array.isArray(options.acceptanceCriteria) && options.acceptanceCriteria.length
796
+ ? options.acceptanceCriteria
797
+ : ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
798
+
799
+ return {
800
+ goal,
801
+ intent: route.intent,
802
+ workCategory: route.workCategory,
803
+ riskFlags: route.riskFlags || {},
804
+ acceptanceCriteria,
805
+ };
806
+ }
package/lib/parity.mjs CHANGED
@@ -104,6 +104,16 @@ function diffSets(expected, actual) {
104
104
  return { missing, extra };
105
105
  }
106
106
 
107
+ // Construct ensures its own MCP servers are present; it does not own the user's
108
+ // full editor MCP list. A non-registry server the user added (e.g. playwright) is
109
+ // not drift — only a MISSING registry server is, which is a real sync regression.
110
+ // A rename still surfaces, since the new name registers as missing. Agent surfaces
111
+ // keep extra-detection (legacy cx-* cleanup), so this applies to MCP kinds only.
112
+
113
+ function mcpStatus(missing) {
114
+ return missing.length === 0 ? 'ok' : 'drift';
115
+ }
116
+
107
117
  function checkFileSurface({ surface, kind, dir, extension, expected }) {
108
118
  if (!fs.existsSync(dir)) return { surface, kind, status: 'absent', dir };
109
119
  const actual = fs.readdirSync(dir)
@@ -245,7 +255,7 @@ function checkVSCode(registry, { homeDir = os.homedir() } = {}) {
245
255
  return {
246
256
  surface: 'vscode',
247
257
  kind: 'mcps',
248
- status: missing.length === 0 && extra.length === 0 ? 'ok' : 'drift',
258
+ status: mcpStatus(missing),
249
259
  paths,
250
260
  expectedCount: expected.length,
251
261
  actualCount: actual.length,
@@ -269,7 +279,7 @@ function checkCursor(registry, { homeDir = os.homedir() } = {}) {
269
279
  return {
270
280
  surface: 'cursor',
271
281
  kind: 'mcps',
272
- status: missing.length === 0 && extra.length === 0 ? 'ok' : 'drift',
282
+ status: mcpStatus(missing),
273
283
  file,
274
284
  expectedCount: expected.length,
275
285
  actualCount: actual.length,
@@ -278,6 +288,90 @@ function checkCursor(registry, { homeDir = os.homedir() } = {}) {
278
288
  };
279
289
  }
280
290
 
291
+ function checkProjectMcp(registry, { projectDir, surface, relPath, configKey }) {
292
+ const file = path.join(projectDir, relPath);
293
+ if (!fs.existsSync(file)) {
294
+ return { surface, scope: 'project', kind: 'mcps', status: 'absent', file };
295
+ }
296
+ let config;
297
+ try {
298
+ config = readJsoncFile(file);
299
+ } catch (err) {
300
+ return { surface, scope: 'project', kind: 'mcps', status: 'unreadable', file, error: err.message };
301
+ }
302
+ const servers = config[configKey] ?? {};
303
+ const expected = Object.keys(registry.mcpServers ?? {}).filter((id) => id !== 'memory');
304
+ const actual = Object.keys(servers).filter((id) => id !== 'memory');
305
+ const { missing, extra } = diffSets(expected, actual);
306
+ return {
307
+ surface,
308
+ scope: 'project',
309
+ kind: 'mcps',
310
+ status: mcpStatus(missing),
311
+ file,
312
+ expectedCount: expected.length,
313
+ actualCount: actual.length,
314
+ missing,
315
+ extra,
316
+ };
317
+ }
318
+
319
+ function checkProjectCursorRules({ projectDir }) {
320
+ const pointer = path.join(projectDir, '.cursor', 'rules', 'construct.mdc');
321
+ if (!fs.existsSync(pointer)) {
322
+ return { surface: 'cursor', scope: 'project', kind: 'rules', status: 'absent', file: pointer };
323
+ }
324
+ const text = fs.readFileSync(pointer, 'utf8');
325
+ const stale = !text.includes('Generated by construct sync');
326
+ return {
327
+ surface: 'cursor',
328
+ scope: 'project',
329
+ kind: 'rules',
330
+ status: stale ? 'drift' : 'ok',
331
+ file: pointer,
332
+ stale,
333
+ };
334
+ }
335
+
336
+ function checkProjectClaudeAgent({ projectDir, registry }) {
337
+ const expected = registry.orchestrator?.name || 'construct';
338
+ const file = path.join(projectDir, '.claude', 'agents', `${expected}.md`);
339
+ if (!fs.existsSync(file)) {
340
+ return { surface: 'claude', scope: 'project', kind: 'agents', status: 'absent', file, expected: [expected] };
341
+ }
342
+ return { surface: 'claude', scope: 'project', kind: 'agents', status: 'ok', file, expected: [expected], actual: [expected] };
343
+ }
344
+
345
+ /**
346
+ * Project-scoped adapter parity for Construct-managed repos (.cx/ or .construct/).
347
+ * Checks `.cursor/mcp.json`, `.vscode/mcp.json`, front-door rule, and construct agent.
348
+ */
349
+ export function checkProjectParity({ rootDir = ROOT_DIR, projectDir = rootDir } = {}) {
350
+ const cxPresent = fs.existsSync(path.join(projectDir, '.cx')) || fs.existsSync(path.join(projectDir, '.construct'));
351
+ if (!cxPresent) {
352
+ return { ok: true, skipped: true, surfaces: [], summary: ['project: not a Construct project'] };
353
+ }
354
+ const registry = loadRegistry(rootDir);
355
+ const surfaces = [
356
+ checkProjectClaudeAgent({ projectDir, registry }),
357
+ checkProjectMcp(registry, { projectDir, surface: 'cursor', relPath: '.cursor/mcp.json', configKey: 'mcpServers' }),
358
+ checkProjectMcp(registry, { projectDir, surface: 'vscode', relPath: '.vscode/mcp.json', configKey: 'servers' }),
359
+ checkProjectCursorRules({ projectDir }),
360
+ ];
361
+ const ok = surfaces.every((s) => s.status === 'ok' || s.status === 'absent');
362
+ const summary = surfaces.map((s) => {
363
+ if (s.status === 'absent') return `${s.surface} (project): missing ${s.kind}`;
364
+ if (s.status === 'unreadable') return `${s.surface} (project): unreadable (${s.error})`;
365
+ if (s.status === 'ok') return `${s.surface} (project): ok`;
366
+ if (s.stale) return `${s.surface} (project): stale construct.mdc — run \`npm run adapters\``;
367
+ const parts = [];
368
+ if (s.missing?.length) parts.push(`missing: ${s.missing.join(', ')}`);
369
+ if (s.extra?.length) parts.push(`extra: ${s.extra.join(', ')}`);
370
+ return `${s.surface} (project): drift — ${parts.join(' · ')}`;
371
+ });
372
+ return { ok, skipped: false, surfaces, summary };
373
+ }
374
+
281
375
  // Names a v1.0.10 install would have populated at user scope (claude, codex,
282
376
  // copilot) before the two-tier sync contract moved specialists to project
283
377
  // scope. Pulled from the live registry so the roster auto-updates as new
@@ -0,0 +1,254 @@
1
+ /**
2
+ * lib/playwright-demo.mjs — workspace-agnostic Playwright demo recording.
3
+ *
4
+ * Spawns `npx playwright test --config <config> <spec>` with env passthrough for
5
+ * artifact reveal, collects video output, and transcodes webm→mp4 when ffmpeg exists.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { spawnSync } from 'node:child_process';
11
+ import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
12
+
13
+ export function resolvePlaywrightPackage(workspace, repoRoot) {
14
+ const candidates = [
15
+ path.join(workspace, 'node_modules', '@playwright', 'test'),
16
+ path.join(repoRoot, 'node_modules', '@playwright', 'test'),
17
+ ];
18
+ return candidates.find((p) => fs.existsSync(p)) || null;
19
+ }
20
+
21
+ export function resolvePlaywrightConfig(workspace) {
22
+ const candidates = ['playwright.config.mjs', 'playwright.config.ts'].map((f) => path.join(workspace, f));
23
+ return candidates.find((p) => fs.existsSync(p)) || null;
24
+ }
25
+
26
+ export function detectPlaywrightDemo({ workspace, cwd = process.cwd(), repoRoot = cwd } = {}) {
27
+ const root = repoRoot || cwd;
28
+ const resolvedWorkspace = path.resolve(root, workspace || '.');
29
+ const missing = [];
30
+ if (!fs.existsSync(path.join(resolvedWorkspace, 'package.json'))) {
31
+ missing.push(`package.json in ${workspace || '.'}`);
32
+ }
33
+ const config = resolvePlaywrightConfig(resolvedWorkspace);
34
+ if (!config) missing.push(`playwright.config.mjs in ${workspace || '.'}`);
35
+ if (!resolvePlaywrightPackage(resolvedWorkspace, root)) {
36
+ missing.push('@playwright/test (npm install in workspace)');
37
+ }
38
+ return {
39
+ ok: true,
40
+ present: missing.length === 0,
41
+ workspace: resolvedWorkspace,
42
+ config,
43
+ missing,
44
+ message: missing.length === 0
45
+ ? 'Playwright demo tooling ready'
46
+ : `Install Playwright demo deps: ${missing.join('; ')}`,
47
+ };
48
+ }
49
+
50
+ export function locateFfmpeg() {
51
+ const result = spawnSync(process.platform === 'win32' ? 'where' : 'which', ['ffmpeg'], { encoding: 'utf8' });
52
+ if (result.status !== 0) return null;
53
+ return result.stdout.trim().split('\n')[0] || null;
54
+ }
55
+
56
+ export function collectVideoFiles(dirs) {
57
+ const collected = [];
58
+ function walk(dir) {
59
+ if (!dir || !fs.existsSync(dir)) return;
60
+ for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
61
+ const p = path.join(dir, ent.name);
62
+ if (ent.isDirectory()) walk(p);
63
+ else if (/\.(webm|mp4)$/.test(ent.name)) collected.push(p);
64
+ }
65
+ }
66
+ for (const dir of dirs) walk(dir);
67
+ return collected;
68
+ }
69
+
70
+ export function newestVideo(paths) {
71
+ if (!paths.length) return null;
72
+ return paths
73
+ .map((p) => ({ p, mtime: fs.statSync(p).mtimeMs }))
74
+ .sort((a, b) => b.mtime - a.mtime)[0].p;
75
+ }
76
+
77
+ export function transcodeWebmToMp4(src, dest) {
78
+ const ffmpeg = locateFfmpeg();
79
+ if (!ffmpeg) return { ok: false, message: 'ffmpeg not on PATH' };
80
+ const result = spawnSync(ffmpeg, [
81
+ '-y', '-i', src,
82
+ '-c:v', 'libx264',
83
+ '-crf', '18',
84
+ '-preset', 'medium',
85
+ '-pix_fmt', 'yuv420p',
86
+ '-movflags', '+faststart',
87
+ dest,
88
+ ], { encoding: 'utf8', timeout: 180_000 });
89
+ if (result.status !== 0) {
90
+ return {
91
+ ok: false,
92
+ message: `ffmpeg failed (exit ${result.status}): ${(result.stderr || '').trim().slice(0, 300)}`,
93
+ };
94
+ }
95
+ return { ok: true, outputPath: dest };
96
+ }
97
+
98
+ export function finalizeDemoVideo({ sourcePath, outputPath, format = 'mp4' }) {
99
+ if (!sourcePath || !fs.existsSync(sourcePath)) {
100
+ return { ok: false, message: 'No video artifact produced' };
101
+ }
102
+ const target = outputPath || sourcePath;
103
+ fs.mkdirSync(path.dirname(target), { recursive: true });
104
+
105
+ if (format === 'mp4' && sourcePath.endsWith('.webm')) {
106
+ const transcode = transcodeWebmToMp4(sourcePath, target);
107
+ if (!transcode.ok) return transcode;
108
+ return { ok: true, outputPath: target, format: 'mp4', engine: 'playwright+ffmpeg' };
109
+ }
110
+
111
+ if (path.resolve(sourcePath) !== path.resolve(target)) {
112
+ fs.copyFileSync(sourcePath, target);
113
+ }
114
+ return {
115
+ ok: true,
116
+ outputPath: target,
117
+ format: path.extname(target).slice(1) || format,
118
+ engine: 'playwright',
119
+ };
120
+ }
121
+
122
+ export function buildArtifactRevealEnv(recording, {
123
+ repoRoot,
124
+ cwd = repoRoot,
125
+ artifactDir = null,
126
+ artifactFile = null,
127
+ env = process.env,
128
+ } = {}) {
129
+ const reveal = recording?.artifactReveal;
130
+ const out = { ...env };
131
+ if (!reveal && !artifactDir && !artifactFile) return out;
132
+
133
+ const file = artifactFile || reveal?.file || reveal?.path || '';
134
+ const staticDir = artifactDir
135
+ || (reveal?.staticDir ? path.resolve(repoRoot || cwd, reveal.staticDir) : '');
136
+
137
+ if (staticDir) out.CONSTRUCT_DEMO_ARTIFACT_DIR = staticDir;
138
+ if (file) out.DEMO_ARTIFACT_FILE = file;
139
+ if (reveal?.mode) out.DEMO_ARTIFACT_REVEAL_MODE = reveal.mode;
140
+ if (recording?.baseUrl) out.BASE_URL = recording.baseUrl;
141
+ if (recording?.webServer && !recording.skipWebServer) {
142
+ out.DEMO_WEB_SERVER_COMMAND = recording.webServer.command;
143
+ out.DEMO_WEB_SERVER_URL = recording.webServer.url;
144
+ out.DEMO_WEB_SERVER_CWD = path.resolve(repoRoot || cwd, recording.webServer.cwd || recording.workspace || '.');
145
+ out.DEMO_WEB_SERVER_TIMEOUT = String(recording.webServer.timeout || 120_000);
146
+ }
147
+ if (recording?.skipWebServer) out.SKIP_WEBSERVER = '1';
148
+ return out;
149
+ }
150
+
151
+ export function resolveRecordingPaths(recording, { repoRoot, cwd = repoRoot } = {}) {
152
+ const root = repoRoot || cwd;
153
+ const workspace = path.resolve(root, recording.workspace || '.');
154
+ const spec = path.isAbsolute(recording.spec)
155
+ ? recording.spec
156
+ : path.resolve(root, recording.spec);
157
+ const config = recording.playwrightConfig
158
+ ? (path.isAbsolute(recording.playwrightConfig)
159
+ ? recording.playwrightConfig
160
+ : path.resolve(root, recording.playwrightConfig))
161
+ : resolvePlaywrightConfig(workspace);
162
+ const outputDir = recording.output?.path
163
+ ? path.dirname(path.resolve(root, recording.output.path))
164
+ : path.join(cwd, '.cx', 'demos');
165
+ return { workspace, spec, config, outputDir };
166
+ }
167
+
168
+ export function recordPlaywrightDemo(recording, {
169
+ cwd = process.cwd(),
170
+ repoRoot,
171
+ outputDir = null,
172
+ outputPath = null,
173
+ format = null,
174
+ artifactDir = null,
175
+ artifactFile = null,
176
+ env = process.env,
177
+ timeout = null,
178
+ } = {}) {
179
+ const root = repoRoot || cwd;
180
+ const name = recording.name || path.basename(recording.spec || '', '.spec.ts');
181
+ const { workspace, spec, config, outputDir: defaultOutDir } = resolveRecordingPaths(recording, { repoRoot: root, cwd });
182
+ const detection = detectPlaywrightDemo({ workspace, repoRoot: root, cwd });
183
+ if (!detection.present) {
184
+ return { ok: false, name, missing: detection.missing, message: detection.message };
185
+ }
186
+ if (!config || !fs.existsSync(config)) {
187
+ return { ok: false, name, message: `Playwright config not found for workspace ${recording.workspace || '.'}` };
188
+ }
189
+ if (!fs.existsSync(spec)) {
190
+ return { ok: false, name, message: `Demo spec not found: ${spec}` };
191
+ }
192
+
193
+ const outDir = outputDir || defaultOutDir;
194
+ fs.mkdirSync(outDir, { recursive: true });
195
+
196
+ const outFormat = format || recording.output?.format || 'mp4';
197
+ const destPath = outputPath
198
+ || (recording.output?.path ? path.resolve(root, recording.output.path) : path.join(outDir, `${name}.${outFormat}`));
199
+
200
+ const spawnEnv = buildArtifactRevealEnv(recording, {
201
+ repoRoot: root,
202
+ cwd,
203
+ artifactDir,
204
+ artifactFile,
205
+ env: {
206
+ ...env,
207
+ DEMO_OUTPUT_DIR: outDir,
208
+ CONSTRUCT_DEMO: name,
209
+ CI: env.CI || '',
210
+ },
211
+ });
212
+
213
+ const args = ['playwright', 'test', '--config', config, spec];
214
+ if (recording.project) args.push(`--project=${recording.project}`);
215
+
216
+ const spawnTimeout = timeout || recording.timeout || 600_000;
217
+ const result = spawnSync('npx', args, {
218
+ cwd: workspace,
219
+ encoding: 'utf8',
220
+ env: sanitizeNpmSpawnEnv(spawnEnv),
221
+ timeout: spawnTimeout,
222
+ });
223
+
224
+ if (result.status !== 0) {
225
+ const detail = (result.stderr || result.stdout || result.error?.message || '').trim().slice(0, 500);
226
+ const exitLabel = result.signal ? `signal ${result.signal}` : `exit ${result.status}`;
227
+ return {
228
+ ok: false,
229
+ name,
230
+ message: `Playwright demo failed (${exitLabel})${detail ? `: ${detail}` : ''}`,
231
+ };
232
+ }
233
+
234
+ const videos = collectVideoFiles([
235
+ path.join(workspace, 'test-results'),
236
+ outDir,
237
+ ]);
238
+ const newest = newestVideo(videos);
239
+ const finalized = finalizeDemoVideo({ sourcePath: newest, outputPath: destPath, format: outFormat });
240
+ if (!finalized.ok) {
241
+ return { ok: false, name, message: finalized.message };
242
+ }
243
+
244
+ return {
245
+ ok: true,
246
+ name,
247
+ outputDir: outDir,
248
+ outputPath: finalized.outputPath,
249
+ format: finalized.format,
250
+ engine: finalized.engine,
251
+ videos: videos.map((p) => path.basename(p)),
252
+ message: `Recorded Playwright demo to ${finalized.outputPath}`,
253
+ };
254
+ }
@@ -87,7 +87,10 @@ instead of letting it turn into a historical log. Durable task status belongs in
87
87
 
88
88
  > Required project state. Keep this file current enough that a new session can resume work quickly.
89
89
 
90
- ## Active Work
90
+ ## What was in progress
91
+
92
+ ## Open issues
93
+ None
91
94
 
92
95
  ## Recent Decisions
93
96
 
@@ -24,9 +24,12 @@ import { buildContextDigest, readContextState } from './context-state.mjs';
24
24
  import {
25
25
  MODEL_OPERATING_PROFILES,
26
26
  resolveExecutionContractModelMetadata,
27
- resolveModelOperatingProfile,
28
27
  selectModelTierForWorkCategory,
29
28
  } from './model-router.mjs';
29
+ import {
30
+ resolveExecutionCapabilityProfile,
31
+ operatingProfileIdFromProfile,
32
+ } from './models/execution-capability-profile.mjs';
30
33
  import { listObservations } from './observation-store.mjs';
31
34
  import { routeRequest } from './orchestration-policy.mjs';
32
35
  import { resolvePromptEntry, resolvePromptMetadata } from './prompt-metadata.mjs';
@@ -208,10 +211,11 @@ export function composePrompt(agentName, {
208
211
 
209
212
  const metadata = resolvePromptMetadata(agentName, { rootDir, registry });
210
213
  const fragments = [];
211
- const selectedProfile = resolveModelOperatingProfile({
214
+ const executionProfile = resolveExecutionCapabilityProfile({
215
+ model: executionContractModel?.selectedModel ?? modelId,
212
216
  envValues: executionContractModel?.profile ? { CONSTRUCT_MODEL_PROFILE: executionContractModel.profile.id } : {},
213
- selectedModel: executionContractModel?.selectedModel ?? modelId,
214
217
  });
218
+ const selectedProfile = MODEL_OPERATING_PROFILES[operatingProfileIdFromProfile(executionProfile)];
215
219
 
216
220
  fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: stripSectionMarkers(readPromptBody(entry.promptFile, rootDir)), tokenBudget: null });
217
221
 
@@ -0,0 +1,24 @@
1
+ /**
2
+ * lib/prompt-validation-contract.mjs — Inline shared validation contract at sync time.
3
+ *
4
+ * Prepends specialists/prompts/_shared/validation-contract.md to every cx-* prompt
5
+ * so assume-nothing discipline is present at runtime without 29 copy-paste edits.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ const MARKER = '<!-- cx:validation-contract -->';
12
+
13
+ export function readValidationContract(root) {
14
+ const p = path.join(root, 'specialists', 'prompts', '_shared', 'validation-contract.md');
15
+ if (!fs.existsSync(p)) return null;
16
+ return fs.readFileSync(p, 'utf8').trim();
17
+ }
18
+
19
+ export function inlineValidationContract(prompt, root, agentName = '(unknown)') {
20
+ if (!prompt || prompt.includes(MARKER)) return prompt;
21
+ const block = readValidationContract(root);
22
+ if (!block) return prompt;
23
+ return `${MARKER}\n\n${block}\n\n${prompt}`;
24
+ }
@@ -41,6 +41,62 @@ function resolveAdapterKey(modelId) {
41
41
  return 'generic';
42
42
  }
43
43
 
44
+ // Declared adapter defaults for sync hot paths when the 24h probe cache is cold.
45
+ // Matches the static `capabilities()` exports in provider-capabilities-*.js.
46
+
47
+ const SYNC_ADAPTER_CAPS = {
48
+ anthropic: {
49
+ cacheControl: true,
50
+ cacheMechanism: 'annotation',
51
+ cacheTTL: { '5m': 300_000, '1h': 1_200_000 },
52
+ structuredOutput: true,
53
+ maxContextWindow: 200_000,
54
+ tokenRatio: 3.5,
55
+ annotationFormat: 'anthropic',
56
+ annotationHeaders: { 'anthropic-version': '2024-10-22' },
57
+ },
58
+ google: {
59
+ cacheControl: true,
60
+ cacheMechanism: 'resource',
61
+ cacheTTL: { '5m': null, '1h': 1_200_000 },
62
+ structuredOutput: true,
63
+ maxContextWindow: 1_000_000,
64
+ tokenRatio: 4,
65
+ annotationFormat: 'google',
66
+ annotationHeaders: null,
67
+ },
68
+ openai: {
69
+ cacheControl: false,
70
+ cacheMechanism: 'automatic',
71
+ cacheTTL: null,
72
+ structuredOutput: true,
73
+ maxContextWindow: 128_000,
74
+ tokenRatio: 4,
75
+ annotationFormat: 'openai',
76
+ annotationHeaders: null,
77
+ },
78
+ deepseek: {
79
+ cacheControl: false,
80
+ cacheMechanism: 'none',
81
+ cacheTTL: null,
82
+ structuredOutput: true,
83
+ maxContextWindow: 128_000,
84
+ tokenRatio: 4,
85
+ annotationFormat: 'none',
86
+ annotationHeaders: null,
87
+ },
88
+ generic: {
89
+ cacheControl: false,
90
+ cacheMechanism: 'none',
91
+ cacheTTL: null,
92
+ structuredOutput: false,
93
+ maxContextWindow: 200_000,
94
+ tokenRatio: 4,
95
+ annotationFormat: 'none',
96
+ annotationHeaders: null,
97
+ },
98
+ };
99
+
44
100
  function readCapabilityCache() {
45
101
  try {
46
102
  if (!existsSync(CAPABILITY_CACHE_PATH)) return {};
@@ -103,17 +159,7 @@ export function resolveProviderCapabilitiesSync(modelId) {
103
159
  // Check cache first (keyed by adapter type, not full model ID)
104
160
  if (cache[adapterKey]) return cache[adapterKey];
105
161
 
106
- // Return generic synchronously (can't load adapter in sync context)
107
- return {
108
- cacheControl: false,
109
- cacheMechanism: 'none',
110
- cacheTTL: null,
111
- structuredOutput: false,
112
- maxContextWindow: 200000,
113
- tokenRatio: 4,
114
- annotationFormat: 'none',
115
- annotationHeaders: null,
116
- };
162
+ return SYNC_ADAPTER_CAPS[adapterKey] || SYNC_ADAPTER_CAPS.generic;
117
163
  }
118
164
 
119
165
  /**