@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
@@ -6,32 +6,19 @@
6
6
  * using the user's active `gh` CLI session.
7
7
  */
8
8
  import http from 'node:http';
9
- import { execSync } from 'node:child_process';
9
+ import { getCopilotToken as resolveCopilotSessionToken } from '../providers/copilot-auth.mjs';
10
10
 
11
11
  const PORT = parseInt(process.argv.find(a => a.startsWith('--port='))?.split('=')[1] || '5174', 10);
12
12
 
13
- let cachedSession = null;
13
+ // Token minting, caching, and refresh live in copilot-auth (the OAuth device
14
+ // flow). The bridge only proxies, so a missing or failed session token surfaces
15
+ // to the client as a 401 with the remediation coming from `construct creds login copilot`.
14
16
 
15
17
  async function getCopilotToken() {
16
- if (cachedSession && cachedSession.expires_at > (Date.now() / 1000) + 60) {
17
- return cachedSession.token;
18
- }
19
-
20
18
  try {
21
- const ghToken = execSync('gh auth token', { encoding: 'utf8' }).trim();
22
- const res = await fetch('https://api.github.com/copilot_internal/v2/token', {
23
- headers: {
24
- 'Authorization': `Bearer ${ghToken}`,
25
- 'Accept': 'application/json',
26
- }
27
- });
28
-
29
- if (!res.ok) throw new Error(`Failed to get session token: ${res.statusText}`);
30
-
31
- cachedSession = await res.json();
32
- return cachedSession.token;
19
+ return await resolveCopilotSessionToken();
33
20
  } catch (err) {
34
- console.error('Error fetching Copilot token:', err.message);
21
+ console.error('Error obtaining Copilot token:', err.message);
35
22
  return null;
36
23
  }
37
24
  }
@@ -61,12 +48,11 @@ const server = http.createServer(async (req, res) => {
61
48
  req.on('end', async () => {
62
49
  try {
63
50
  const payload = JSON.parse(body);
64
-
65
- // Map common model names to Copilot equivalents if needed
66
- // For now, we pass them through or default to gpt-4o
67
- const model = payload.model?.includes('gpt-4o') ? 'gpt-4o' :
68
- payload.model?.includes('claude-3.5-sonnet') ? 'claude-3.5-sonnet' :
69
- 'gpt-4o';
51
+
52
+ // Pass the requested model through unchanged so the caller's selection is
53
+ // honored; Copilot validates it against the account's available models and
54
+ // returns a clear error for an unsupported id rather than silently swapping.
55
+ const model = payload.model || 'gpt-4o';
70
56
 
71
57
  const copilotRes = await fetch('https://api.githubcopilot.com/chat/completions', {
72
58
  method: 'POST',
@@ -75,7 +61,8 @@ const server = http.createServer(async (req, res) => {
75
61
  'Content-Type': 'application/json',
76
62
  'Accept': 'application/json',
77
63
  'X-Github-Api-Version': '2023-07-07',
78
- 'Editor-Version': 'vscode/1.90.0', // Spoof VS Code for compatibility
64
+ 'Copilot-Integration-Id': 'vscode-chat',
65
+ 'Editor-Version': 'vscode/1.90.0',
79
66
  },
80
67
  body: JSON.stringify({
81
68
  ...payload,
@@ -0,0 +1,156 @@
1
+ /**
2
+ * lib/capability-ledger.mjs — behavior-to-test traceability for release-critical surfaces.
3
+ *
4
+ * The ledger describes observable product capabilities, not implementation units. A
5
+ * capability entry identifies the outcome, failure modes, affected paths, fixtures,
6
+ * and test files that verify it. Validation is deliberately bidirectional: every
7
+ * listed test must declare the capability id in its source, preventing an unmaintained JSON
8
+ * entry from claiming coverage when its test source lacks the marker.
9
+ */
10
+
11
+ import fs from 'node:fs';
12
+ import path from 'node:path';
13
+
14
+ export const TEST_LAYERS = Object.freeze([
15
+ 'unit',
16
+ 'integration',
17
+ 'functional',
18
+ 'host-emulation',
19
+ 'live-provider',
20
+ 'visual',
21
+ ]);
22
+
23
+ export const CRITICALITIES = Object.freeze(['release', 'important', 'standard']);
24
+
25
+ function findConstructRoot(startPath = process.cwd()) {
26
+ let current = path.resolve(startPath);
27
+ while (true) {
28
+ if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
29
+ const parent = path.dirname(current);
30
+ if (parent === current) return path.resolve(startPath);
31
+ current = parent;
32
+ }
33
+ }
34
+
35
+ export function defaultLedgerPath(rootDir = process.cwd()) {
36
+ return path.join(findConstructRoot(rootDir), 'tests', 'capabilities', 'ledger.json');
37
+ }
38
+
39
+ export function loadCapabilityLedger({ rootDir } = {}) {
40
+ const filePath = defaultLedgerPath(rootDir);
41
+ return { filePath, ledger: JSON.parse(fs.readFileSync(filePath, 'utf8')) };
42
+ }
43
+
44
+ function hasText(value) {
45
+ return typeof value === 'string' && value.trim().length > 0;
46
+ }
47
+
48
+ function hasStringArray(value) {
49
+ return Array.isArray(value) && value.length > 0 && value.every(hasText);
50
+ }
51
+
52
+ function testDeclaresCapability(testPath, id) {
53
+ try {
54
+ return fs.readFileSync(testPath, 'utf8').includes(`@capability ${id}`);
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ export function validateCapabilityLedger({ rootDir, ledger: suppliedLedger } = {}) {
61
+ const root = findConstructRoot(rootDir);
62
+ const loaded = suppliedLedger ? { filePath: null, ledger: suppliedLedger } : loadCapabilityLedger({ rootDir: root });
63
+ const ledger = loaded.ledger;
64
+ const errors = [];
65
+ const warnings = [];
66
+ const ids = new Set();
67
+ let mappedTestCount = 0;
68
+
69
+ if (ledger?.version !== 1) errors.push('ledger.version must equal 1');
70
+ if (!Array.isArray(ledger?.capabilities) || ledger.capabilities.length === 0) {
71
+ errors.push('ledger.capabilities must contain at least one capability');
72
+ }
73
+
74
+ for (const capability of ledger?.capabilities ?? []) {
75
+ const id = capability?.id;
76
+ if (!hasText(id) || !/^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*$/.test(id)) {
77
+ errors.push(`capability has invalid id: ${String(id)}`);
78
+ continue;
79
+ }
80
+ if (ids.has(id)) errors.push(`${id}: duplicate capability id`);
81
+ ids.add(id);
82
+
83
+ if (!hasText(capability.outcome)) errors.push(`${id}: outcome must describe an observable user or system result`);
84
+ if (!hasStringArray(capability.failureModes)) errors.push(`${id}: failureModes must name at least one failure mode`);
85
+ if (!hasStringArray(capability.changePaths)) errors.push(`${id}: changePaths must identify affected implementation paths`);
86
+ if (!CRITICALITIES.includes(capability.criticality)) errors.push(`${id}: criticality must be one of ${CRITICALITIES.join(', ')}`);
87
+ if (!hasStringArray(capability.assertions)) errors.push(`${id}: assertions must describe observable checks`);
88
+ if (!Array.isArray(capability.tests) || capability.tests.length === 0) {
89
+ errors.push(`${id}: tests must contain at least one test reference`);
90
+ continue;
91
+ }
92
+
93
+ const layers = new Set();
94
+ for (const test of capability.tests) {
95
+ const relPath = test?.path;
96
+ if (!hasText(relPath)) {
97
+ errors.push(`${id}: test path is required`);
98
+ continue;
99
+ }
100
+ const absolutePath = path.join(root, relPath);
101
+ if (!fs.existsSync(absolutePath)) errors.push(`${id}: test file does not exist: ${relPath}`);
102
+ if (!TEST_LAYERS.includes(test?.layer)) errors.push(`${id}: ${relPath} has invalid layer ${String(test?.layer)}`);
103
+ if (!testDeclaresCapability(absolutePath, id)) errors.push(`${id}: ${relPath} must declare @capability ${id}`);
104
+ layers.add(test?.layer);
105
+ mappedTestCount += 1;
106
+ }
107
+
108
+ if (capability.criticality === 'release' && !['functional', 'host-emulation', 'integration', 'visual'].some((layer) => layers.has(layer))) {
109
+ errors.push(`${id}: release-critical capability needs integration, functional, host-emulation, or visual coverage`);
110
+ }
111
+
112
+ for (const fixture of capability.fixtures ?? []) {
113
+ if (!hasText(fixture) || !fs.existsSync(path.join(root, fixture))) errors.push(`${id}: fixture does not exist: ${String(fixture)}`);
114
+ }
115
+ if (!Array.isArray(capability.fixtures) || capability.fixtures.length === 0) warnings.push(`${id}: no committed fixture declared`);
116
+ }
117
+
118
+ return {
119
+ filePath: loaded.filePath,
120
+ capabilityCount: ledger?.capabilities?.length ?? 0,
121
+ mappedTestCount,
122
+ errors,
123
+ warnings,
124
+ pass: errors.length === 0,
125
+ };
126
+ }
127
+
128
+ export function formatCapabilityLedgerAudit(result) {
129
+ const lines = [
130
+ 'Construct capability ledger audit',
131
+ '════════════════════════════════',
132
+ ` Capabilities: ${result.capabilityCount}`,
133
+ ` Test references: ${result.mappedTestCount}`,
134
+ '',
135
+ ];
136
+ if (result.errors.length) {
137
+ lines.push(` ✗ Errors (${result.errors.length}):`);
138
+ result.errors.forEach((error) => lines.push(` - ${error}`));
139
+ } else {
140
+ lines.push(' ✓ Every mapped test declares its observable capability');
141
+ }
142
+ if (result.warnings.length) {
143
+ lines.push('', ` ⚠ Warnings (${result.warnings.length}):`);
144
+ result.warnings.forEach((warning) => lines.push(` - ${warning}`));
145
+ }
146
+ lines.push('', result.pass ? ' Result: PASS' : ' Result: FAIL');
147
+ return `${lines.join('\n')}\n`;
148
+ }
149
+
150
+ export function runCapabilityLedgerAuditCli(args = [], { rootDir } = {}) {
151
+ const result = validateCapabilityLedger({ rootDir });
152
+ if (args.includes('--json')) process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
153
+ else process.stdout.write(formatCapabilityLedgerAudit(result));
154
+ if (!result.pass) process.exitCode = 1;
155
+ return result;
156
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * lib/certification/artifact-fixtures.mjs — golden artifact fixtures per manifest type.
3
+ *
4
+ * Builds minimal valid markdown derived from specialists/artifact-manifest.json
5
+ * structure and visual requirements so release-gate tests can reference stable
6
+ * fixtures under tests/fixtures/artifacts/<type>/.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+ import { artifactTypes, getArtifactEntry } from '../artifact-manifest.mjs';
12
+ import { validateArtifactRelease } from '../artifact-release-gate.mjs';
13
+
14
+ function findConstructRoot(startPath = process.cwd()) {
15
+ let current = path.resolve(startPath);
16
+ while (true) {
17
+ if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
18
+ const parent = path.dirname(current);
19
+ if (parent === current) return path.resolve(startPath);
20
+ current = parent;
21
+ }
22
+ }
23
+
24
+ function proseBlock(label, sentences = 3) {
25
+ const parts = [];
26
+ for (let i = 0; i < sentences; i += 1) {
27
+ parts.push(`This paragraph supports the ${label} section with observable evidence. Source: https://example.com/fixture (accessed 2026-06-22).`);
28
+ }
29
+ return parts.join(' ');
30
+ }
31
+
32
+ function mermaidForDiagram(diagram) {
33
+ if (diagram === 'sequenceDiagram') {
34
+ return '```mermaid\nsequenceDiagram\n Client->>Service: request\n Service-->>Client: response\n```';
35
+ }
36
+ return '```mermaid\nflowchart LR\n A[Start] --> B[End]\n```';
37
+ }
38
+
39
+ function tableForColumns(columns) {
40
+ const header = `| ${columns.join(' | ')} |`;
41
+ const sep = `| ${columns.map(() => '---').join(' | ')} |`;
42
+ const row = `| ${columns.map((c) => `${c} value`).join(' | ')} |`;
43
+ return [header, sep, row].join('\n');
44
+ }
45
+
46
+ export function goldenFixtureRelPath(type) {
47
+ return path.join('tests', 'fixtures', 'artifacts', type, 'golden.md');
48
+ }
49
+
50
+ export function goldenFixturePath(type, { rootDir } = {}) {
51
+ const root = findConstructRoot(rootDir);
52
+ return path.join(root, goldenFixtureRelPath(type));
53
+ }
54
+
55
+ export function buildGoldenFixtureMarkdown(type, { rootDir } = {}) {
56
+ const root = findConstructRoot(rootDir);
57
+ const entry = getArtifactEntry(type, { rootDir: root });
58
+ if (!entry) throw new Error(`unknown artifact type: ${type}`);
59
+
60
+ const lines = [
61
+ '---',
62
+ `description: Golden ${type} fixture for artifact release-gate certification tests.`,
63
+ `cx_fixture_type: ${type}`,
64
+ `cx_fixture_source: ${entry.template}`,
65
+ '---',
66
+ '',
67
+ `# Golden fixture: ${type}`,
68
+ '',
69
+ ];
70
+
71
+ const proseMinimum = entry.releaseGate?.proseMinimum ?? 0;
72
+ let proseAdded = 0;
73
+
74
+ for (const section of entry.structureRequirements ?? []) {
75
+ lines.push(`## ${section}`, '');
76
+ if (proseAdded < proseMinimum) {
77
+ lines.push(proseBlock(section));
78
+ proseAdded += 1;
79
+ } else {
80
+ lines.push(`Fixture content for ${section}.`);
81
+ }
82
+ lines.push('');
83
+ }
84
+
85
+ for (const visual of entry.visualRequirements ?? []) {
86
+ if (visual.check === 'artifact-has-mermaid') {
87
+ lines.push(mermaidForDiagram(visual.diagram ?? 'flowchart'), '');
88
+ }
89
+ if (visual.check === 'artifact-table-has-columns' && Array.isArray(visual.columns)) {
90
+ lines.push(tableForColumns(visual.columns), '');
91
+ }
92
+ }
93
+
94
+ while (proseAdded < proseMinimum) {
95
+ lines.push(proseBlock('supplemental'), '');
96
+ proseAdded += 1;
97
+ }
98
+
99
+ if (entry.releaseGate?.citationLint) {
100
+ lines.push('## References', '', '- https://example.com/fixture-source (accessed 2026-06-22)', '');
101
+ }
102
+
103
+ return `${lines.join('\n').trim()}\n`;
104
+ }
105
+
106
+ export function writeGoldenFixtures({ rootDir } = {}) {
107
+ const root = findConstructRoot(rootDir);
108
+ const written = [];
109
+ const failures = [];
110
+
111
+ for (const type of artifactTypes({ rootDir: root })) {
112
+ const rel = goldenFixtureRelPath(type);
113
+ const abs = path.join(root, rel);
114
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
115
+ const markdown = buildGoldenFixtureMarkdown(type, { rootDir: root });
116
+ fs.writeFileSync(abs, markdown);
117
+ const validation = validateArtifactRelease({ filePath: abs, type, rootDir: root });
118
+ if (!validation.ok) failures.push({ type, errors: validation.errors });
119
+ written.push({ type, path: rel, ok: validation.ok });
120
+ }
121
+
122
+ return { written, failures };
123
+ }
124
+
125
+ export function listGoldenFixturePaths({ rootDir } = {}) {
126
+ const root = findConstructRoot(rootDir);
127
+ return artifactTypes({ rootDir: root }).map((type) => ({
128
+ type,
129
+ path: goldenFixtureRelPath(type),
130
+ exists: fs.existsSync(goldenFixturePath(type, { rootDir: root })),
131
+ }));
132
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * lib/certification/artifact-gates.mjs — per-type structural and visual certification matrix.
3
+ *
4
+ * Validates golden artifact fixtures under tests/fixtures/artifacts/<type>/ against
5
+ * artifact-manifest release gates (layout, accessibility hints, visual requirements).
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ import { artifactTypes, getArtifactEntry } from '../artifact-manifest.mjs';
12
+ import { validateArtifactRelease } from '../artifact-release-gate.mjs';
13
+ import { goldenFixturePath } from './artifact-fixtures.mjs';
14
+
15
+ function findConstructRoot(startPath = process.cwd()) {
16
+ let current = path.resolve(startPath);
17
+ while (true) {
18
+ if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
19
+ const parent = path.dirname(current);
20
+ if (parent === current) return path.resolve(startPath);
21
+ current = parent;
22
+ }
23
+ }
24
+
25
+ export function artifactGateMatrix({ rootDir } = {}) {
26
+ const root = findConstructRoot(rootDir);
27
+ return artifactTypes({ rootDir: root }).map((type) => {
28
+ const entry = getArtifactEntry(type, { rootDir: root });
29
+ return {
30
+ type,
31
+ structureRequirements: entry?.structureRequirements ?? [],
32
+ visualRequirements: (entry?.visualRequirements ?? []).map((v) => v.check),
33
+ releaseGate: entry?.releaseGate ?? {},
34
+ };
35
+ });
36
+ }
37
+
38
+ export function validateAllGoldenArtifactGates({ rootDir } = {}) {
39
+ const root = findConstructRoot(rootDir);
40
+ const results = [];
41
+ const errors = [];
42
+ for (const type of artifactTypes({ rootDir: root })) {
43
+ const filePath = goldenFixturePath(type, { rootDir: root });
44
+ if (!fs.existsSync(filePath)) {
45
+ errors.push(`missing golden fixture: ${type}`);
46
+ results.push({ type, pass: false, detail: 'missing fixture' });
47
+ continue;
48
+ }
49
+ const validation = validateArtifactRelease({ filePath, type, rootDir: root });
50
+ results.push({ type, pass: validation.ok === true, errors: validation.errors ?? [] });
51
+ if (!validation.ok) errors.push(`${type}: ${validation.errors?.[0] ?? 'gate failed'}`);
52
+ }
53
+ return { pass: errors.length === 0, results, errors, matrix: artifactGateMatrix({ rootDir: root }) };
54
+ }
55
+
56
+ export function writeArtifactGateMatrixDoc({ rootDir } = {}) {
57
+ const root = findConstructRoot(rootDir);
58
+ const out = path.join(root, 'tests', 'certification', 'artifacts', 'gate-matrix.json');
59
+ fs.mkdirSync(path.dirname(out), { recursive: true });
60
+ const payload = { generatedAt: new Date().toISOString(), matrix: artifactGateMatrix({ rootDir: root }) };
61
+ fs.writeFileSync(out, `${JSON.stringify(payload, null, 2)}\n`);
62
+ return out;
63
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * lib/certification/artifact-provenance.mjs — provenance and accessibility certification.
3
+ *
4
+ * Validates golden artifact fixtures for cx_doc_id / fixture provenance stamps and
5
+ * WCAG-oriented structure (heading order, alt-text placeholders on visual types).
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ import { artifactTypes } from '../artifact-manifest.mjs';
12
+ import { goldenFixturePath } from './artifact-fixtures.mjs';
13
+ import { splitFrontmatter } from '../specialists/prompt-schema.mjs';
14
+
15
+ const RELEASE_CRITICAL_TYPES = new Set(['prd', 'adr', 'rfc', 'threat-model', 'security-review']);
16
+
17
+ function findConstructRoot(startPath = process.cwd()) {
18
+ let current = path.resolve(startPath);
19
+ while (true) {
20
+ if (fs.existsSync(path.join(current, 'package.json'))) return current;
21
+ const parent = path.dirname(current);
22
+ if (parent === current) return path.resolve(startPath);
23
+ current = parent;
24
+ }
25
+ }
26
+
27
+ function hasProvenanceStamp(frontmatter, body) {
28
+ if (frontmatter?.cx_doc_id) return true;
29
+ if (frontmatter?.cx_fixture_type) return true;
30
+ if (frontmatter?.cx_fixture_source) return true;
31
+ if (/\bcx_doc_id:/i.test(body.slice(0, 500))) return true;
32
+ return false;
33
+ }
34
+
35
+ function headingLevel(line) {
36
+ const m = /^(#{1,6})\s/.exec(line.trim());
37
+ return m ? m[1].length : null;
38
+ }
39
+
40
+ function validateHeadingStructure(body) {
41
+ const errors = [];
42
+ const levels = body.split('\n').map(headingLevel).filter((l) => l != null);
43
+ if (!levels.includes(1)) errors.push('missing h1 (# heading)');
44
+ let prev = 0;
45
+ for (const level of levels) {
46
+ if (prev > 0 && level > prev + 1) {
47
+ errors.push(`heading skip: h${prev} → h${level}`);
48
+ break;
49
+ }
50
+ prev = level;
51
+ }
52
+ return errors;
53
+ }
54
+
55
+ function validateAltPlaceholders(body, type) {
56
+ if (!['prd', 'architecture-overview', 'system-design'].includes(type)) return [];
57
+ const hasImage = /!\[[^\]]*\]\([^)]+\)/.test(body);
58
+ const hasPlaceholder = /alt\s*text|describe the (image|diagram)|\[alt:/i.test(body);
59
+ if (hasImage && !hasPlaceholder) {
60
+ return ['image without alt-text placeholder guidance'];
61
+ }
62
+ return [];
63
+ }
64
+
65
+ export function validateArtifactProvenance(type, { rootDir, strict = true } = {}) {
66
+ const root = findConstructRoot(rootDir);
67
+ const filePath = goldenFixturePath(type, { rootDir: root });
68
+ const errors = [];
69
+
70
+ if (!fs.existsSync(filePath)) {
71
+ return { type, pass: false, errors: [`missing golden fixture: ${type}`] };
72
+ }
73
+
74
+ const raw = fs.readFileSync(filePath, 'utf8');
75
+ const { frontmatter, body } = splitFrontmatter(raw);
76
+
77
+ if (!hasProvenanceStamp(frontmatter, body)) {
78
+ errors.push(`${type}: missing provenance stamp (cx_doc_id or cx_fixture_*)`);
79
+ }
80
+
81
+ errors.push(...validateHeadingStructure(body).map((e) => `${type}: ${e}`));
82
+ errors.push(...validateAltPlaceholders(body, type).map((e) => `${type}: ${e}`));
83
+
84
+ const blocking = strict && RELEASE_CRITICAL_TYPES.has(type) && errors.length > 0;
85
+ return { type, pass: errors.length === 0, blocking, errors };
86
+ }
87
+
88
+ export function validateAllArtifactProvenance({ rootDir, strict = true } = {}) {
89
+ const results = [];
90
+ const errors = [];
91
+ for (const type of artifactTypes({ rootDir: findConstructRoot(rootDir) })) {
92
+ const result = validateArtifactProvenance(type, { rootDir, strict });
93
+ results.push(result);
94
+ if (!result.pass) errors.push(...result.errors);
95
+ }
96
+ return { pass: errors.length === 0, strict, results, errors };
97
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * lib/certification/canonical-scenarios.mjs — versioned canonical demo scenario catalog.
3
+ *
4
+ * Loads tests/certification/demos/canonical-scenarios.json and validates that
5
+ * cited tape and theme paths exist under templates/demos/.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+
11
+ function findConstructRoot(startPath = process.cwd()) {
12
+ let current = path.resolve(startPath);
13
+ while (true) {
14
+ if (fs.existsSync(path.join(current, 'package.json')) && fs.existsSync(path.join(current, 'tests'))) return current;
15
+ const parent = path.dirname(current);
16
+ if (parent === current) return path.resolve(startPath);
17
+ current = parent;
18
+ }
19
+ }
20
+
21
+ export const CANONICAL_DEMO_SCHEMA = 'construct/certification/canonical-demos/1';
22
+
23
+ export function defaultCanonicalScenariosPath(rootDir = process.cwd()) {
24
+ return path.join(findConstructRoot(rootDir), 'tests', 'certification', 'demos', 'canonical-scenarios.json');
25
+ }
26
+
27
+ export function loadCanonicalScenarios({ rootDir } = {}) {
28
+ const root = findConstructRoot(rootDir);
29
+ const filePath = defaultCanonicalScenariosPath(root);
30
+ const catalog = JSON.parse(fs.readFileSync(filePath, 'utf8'));
31
+ return { root, filePath, catalog };
32
+ }
33
+
34
+ export function validateCanonicalScenarios({ rootDir, catalog: supplied } = {}) {
35
+ const root = findConstructRoot(rootDir);
36
+ const catalog = supplied ?? JSON.parse(fs.readFileSync(defaultCanonicalScenariosPath(root), 'utf8'));
37
+ const errors = [];
38
+
39
+ if (catalog.version !== 1) errors.push('canonical-scenarios.version must equal 1');
40
+ if (catalog.schema !== CANONICAL_DEMO_SCHEMA) errors.push(`canonical-scenarios.schema must equal ${CANONICAL_DEMO_SCHEMA}`);
41
+ if (!Array.isArray(catalog.demos) || catalog.demos.length < 2) errors.push('canonical-scenarios.demos must contain at least two entries');
42
+
43
+ const ids = new Set();
44
+ for (const demo of catalog.demos ?? []) {
45
+ if (!demo.id || typeof demo.id !== 'string') {
46
+ errors.push('demo entry missing id');
47
+ continue;
48
+ }
49
+ if (ids.has(demo.id)) errors.push(`${demo.id}: duplicate demo id`);
50
+ ids.add(demo.id);
51
+
52
+ const tape = demo.tape ?? demo.tapePath;
53
+ if (!tape) errors.push(`${demo.id}: tape path is required`);
54
+ else if (!fs.existsSync(path.join(root, tape))) errors.push(`${demo.id}: tape does not exist: ${tape}`);
55
+
56
+ for (const ref of demo.references ?? []) {
57
+ if (!fs.existsSync(path.join(root, ref))) errors.push(`${demo.id}: reference does not exist: ${ref}`);
58
+ }
59
+ if (demo.vhsTheme && !fs.existsSync(path.join(root, demo.vhsTheme))) {
60
+ errors.push(`${demo.id}: vhsTheme does not exist: ${demo.vhsTheme}`);
61
+ }
62
+ if (demo.script && !fs.existsSync(path.join(root, demo.script))) {
63
+ errors.push(`${demo.id}: script does not exist: ${demo.script}`);
64
+ }
65
+ }
66
+
67
+ const required = ['agentic-platforms-prd', 'construct-cockpit'];
68
+ for (const id of required) {
69
+ if (!ids.has(id)) errors.push(`required demo missing: ${id}`);
70
+ }
71
+
72
+ return {
73
+ filePath: defaultCanonicalScenariosPath(root),
74
+ demoCount: catalog.demos?.length ?? 0,
75
+ errors,
76
+ pass: errors.length === 0,
77
+ };
78
+ }