@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
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Decision",
4
+ "description": "Output from cx-architect: a documented architectural or design decision",
5
+ "type": "object",
6
+ "required": ["type", "goal", "approach", "tasks", "risks", "acceptanceCriteria"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["decision"] },
9
+ "goal": { "type": "string", "description": "Objective restated in one sentence" },
10
+ "approach": { "type": "string", "description": "Chosen strategy and rationale" },
11
+ "alternativesConsidered": {
12
+ "type": "array",
13
+ "items": { "type": "string" }
14
+ },
15
+ "tasks": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["description", "owner", "complexity"],
20
+ "properties": {
21
+ "description": { "type": "string" },
22
+ "owner": { "type": "string" },
23
+ "complexity": { "type": "string", "enum": ["S", "M", "L"] }
24
+ }
25
+ }
26
+ },
27
+ "risks": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": "object",
31
+ "required": ["description", "mitigation"],
32
+ "properties": {
33
+ "description": { "type": "string" },
34
+ "mitigation": { "type": "string" }
35
+ }
36
+ }
37
+ },
38
+ "acceptanceCriteria": {
39
+ "type": "array",
40
+ "items": { "type": "string" },
41
+ "minItems": 1
42
+ },
43
+ "doNotTouch": {
44
+ "type": "array",
45
+ "items": { "type": "string" },
46
+ "description": "Files or systems that must stay unchanged"
47
+ }
48
+ },
49
+ "additionalProperties": false
50
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Implementation",
4
+ "description": "Output from cx-engineer: summary of changes made",
5
+ "type": "object",
6
+ "required": ["type", "status", "filesChanged", "verificationChecklist"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["implementation"] },
9
+ "status": {
10
+ "type": "string",
11
+ "enum": ["DONE", "BLOCKED", "NEEDS_MAIN_INPUT"]
12
+ },
13
+ "filesChanged": {
14
+ "type": "array",
15
+ "items": {
16
+ "type": "object",
17
+ "required": ["path", "action"],
18
+ "properties": {
19
+ "path": { "type": "string" },
20
+ "action": { "type": "string", "enum": ["created", "modified", "deleted"] },
21
+ "summary": { "type": "string" }
22
+ }
23
+ }
24
+ },
25
+ "verificationChecklist": {
26
+ "type": "object",
27
+ "properties": {
28
+ "compilesWithoutErrors": { "type": "boolean" },
29
+ "existingTestsPass": { "type": "boolean" },
30
+ "newBehaviorHasCoverage": { "type": "boolean" },
31
+ "noHardcodedSecrets": { "type": "boolean" },
32
+ "noFileOver800Lines": { "type": "boolean" }
33
+ },
34
+ "required": ["compilesWithoutErrors", "existingTestsPass", "newBehaviorHasCoverage", "noHardcodedSecrets", "noFileOver800Lines"]
35
+ },
36
+ "blockerDescription": {
37
+ "type": "string",
38
+ "description": "Required when status is BLOCKED or NEEDS_MAIN_INPUT"
39
+ },
40
+ "needsMainInput": {
41
+ "type": "object",
42
+ "description": "Required when status is NEEDS_MAIN_INPUT",
43
+ "properties": {
44
+ "taskKey": { "type": "string" },
45
+ "question": { "type": "string" },
46
+ "safeDefault": { "type": "string" }
47
+ }
48
+ }
49
+ },
50
+ "additionalProperties": false
51
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "ReviewReport",
4
+ "description": "Output from cx-reviewer or cx-security: findings from a code or security review",
5
+ "type": "object",
6
+ "required": ["type", "target", "verdict", "findings"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["review-report"] },
9
+ "target": { "type": "string", "description": "Files or scope reviewed" },
10
+ "verdict": {
11
+ "type": "string",
12
+ "enum": ["APPROVED", "APPROVED_WITH_WARNINGS", "BLOCKED"],
13
+ "description": "APPROVED = no CRITICAL/HIGH. APPROVED_WITH_WARNINGS = only HIGH (merge with caution). BLOCKED = CRITICAL found."
14
+ },
15
+ "findings": {
16
+ "type": "array",
17
+ "items": {
18
+ "type": "object",
19
+ "required": ["severity", "category", "location", "description", "fix"],
20
+ "properties": {
21
+ "severity": { "type": "string", "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"] },
22
+ "category": { "type": "string", "description": "e.g. correctness, security, regression, coverage, maintainability" },
23
+ "location": { "type": "string", "description": "file:line" },
24
+ "description": { "type": "string" },
25
+ "fix": { "type": "string", "description": "Concrete remediation" }
26
+ }
27
+ }
28
+ },
29
+ "summary": { "type": "string", "description": "One-paragraph summary of overall quality" }
30
+ },
31
+ "additionalProperties": false
32
+ }
@@ -0,0 +1,43 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "TestReport",
4
+ "description": "Output from cx-qa: results of test planning and execution",
5
+ "type": "object",
6
+ "required": ["type", "verdict", "passed", "failed", "coverage"],
7
+ "properties": {
8
+ "type": { "type": "string", "enum": ["test-report"] },
9
+ "verdict": {
10
+ "type": "string",
11
+ "enum": ["PASS", "FAIL"],
12
+ "description": "PASS = all tests green and coverage meets threshold. FAIL = failing tests or coverage below threshold."
13
+ },
14
+ "passed": { "type": "integer", "minimum": 0 },
15
+ "failed": { "type": "integer", "minimum": 0 },
16
+ "skipped": { "type": "integer", "minimum": 0 },
17
+ "coverage": {
18
+ "type": "object",
19
+ "properties": {
20
+ "overall": { "type": "number", "description": "0.0–100.0 percentage" },
21
+ "meetsThreshold": { "type": "boolean" },
22
+ "threshold": { "type": "number" }
23
+ }
24
+ },
25
+ "failingTests": {
26
+ "type": "array",
27
+ "items": {
28
+ "type": "object",
29
+ "required": ["name", "error"],
30
+ "properties": {
31
+ "name": { "type": "string" },
32
+ "error": { "type": "string" }
33
+ }
34
+ }
35
+ },
36
+ "gaps": {
37
+ "type": "array",
38
+ "items": { "type": "string" },
39
+ "description": "Untested behaviors that should have tests"
40
+ }
41
+ },
42
+ "additionalProperties": false
43
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * lib/contracts/construct-handoff.mjs — normalize construct→orchestrator handoffs.
3
+ *
4
+ * MCP callers often pass only `goal`; enrich from orchestration_policy before
5
+ * validateHandoff so incomplete packets do not flood contract-violations.jsonl.
6
+ */
7
+
8
+ import { buildConstructToOrchestratorPacket, routeRequest } from '../orchestration-policy.mjs';
9
+
10
+ export const CONSTRUCT_ORCHESTRATOR_REQUIRED = Object.freeze([
11
+ 'goal',
12
+ 'intent',
13
+ 'workCategory',
14
+ 'riskFlags',
15
+ 'acceptanceCriteria',
16
+ ]);
17
+
18
+ export function needsConstructHandoffEnrichment(artifact) {
19
+ if (!artifact || typeof artifact !== 'object') return false;
20
+ return CONSTRUCT_ORCHESTRATOR_REQUIRED.some((field) => !Object.prototype.hasOwnProperty.call(artifact, field));
21
+ }
22
+
23
+ /**
24
+ * @param {object} artifact — partial or full handoff packet
25
+ * @param {object} [opts]
26
+ * @param {string} [opts.request]
27
+ * @param {number} [opts.fileCount]
28
+ * @param {number} [opts.moduleCount]
29
+ * @param {boolean} [opts.introducesContract]
30
+ */
31
+ export function enrichConstructOrchestratorHandoff(artifact, opts = {}) {
32
+ if (!needsConstructHandoffEnrichment(artifact)) return artifact;
33
+ const request = String(opts.request || artifact.goal || artifact.request || '').trim();
34
+ const routeOpts = {
35
+ request,
36
+ fileCount: opts.fileCount ?? artifact.fileCount ?? 0,
37
+ moduleCount: opts.moduleCount ?? artifact.moduleCount ?? 0,
38
+ introducesContract: opts.introducesContract ?? artifact.introducesContract ?? false,
39
+ };
40
+ const built = buildConstructToOrchestratorPacket({
41
+ ...routeOpts,
42
+ goal: artifact.goal,
43
+ acceptanceCriteria: artifact.acceptanceCriteria,
44
+ });
45
+ if (built) return { ...built, ...artifact };
46
+
47
+ const route = routeRequest(routeOpts);
48
+ const goal = String(artifact.goal || request || 'User-requested work').trim();
49
+ const acceptanceCriteria = Array.isArray(artifact.acceptanceCriteria) && artifact.acceptanceCriteria.length
50
+ ? artifact.acceptanceCriteria
51
+ : ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
52
+ return {
53
+ goal,
54
+ intent: route.intent,
55
+ workCategory: route.workCategory,
56
+ riskFlags: route.riskFlags || {},
57
+ acceptanceCriteria,
58
+ ...artifact,
59
+ };
60
+ }
@@ -4,7 +4,7 @@
4
4
  * Three validation tiers:
5
5
  * 1. Schema shape: contracts.json conforms to contracts.schema.json (minimal
6
6
  * validator — top-level required fields, contract entry required fields).
7
- * 2. Cross-file refs: every output.schema points to a real file in lib/schemas/,
7
+ * 2. Cross-file refs: every output.schema points to a real file in lib/contract-schemas/,
8
8
  * every producer/consumer name resolves to an agent or persona in
9
9
  * specialists/registry.json, every well-known event/intake string is reachable.
10
10
  * 3. Runtime handoff: a single artifact validated against the schema referenced
@@ -19,6 +19,10 @@
19
19
  import { existsSync, readFileSync } from 'node:fs';
20
20
  import { join, dirname, resolve } from 'node:path';
21
21
  import { fileURLToPath } from 'node:url';
22
+ import {
23
+ missingRequiredReviewers,
24
+ } from '../artifact-reviewers.mjs';
25
+ import { inferArtifactTypeFromPath } from '../artifact-type-from-path.mjs';
22
26
  import { POSTCONDITIONS, validateBinaryPostconditions } from '../specialists/postconditions.mjs';
23
27
  import { logViolation } from './violation-log.mjs';
24
28
 
@@ -244,7 +248,12 @@ export function validateHandoff(opts = {}) {
244
248
  const absPath = isAbsolutePath(declaredArtifactPath)
245
249
  ? declaredArtifactPath
246
250
  : join(repoRoot, declaredArtifactPath);
247
- const pcErrors = validateArtifactPostconditions({ contract, artifactPath: absPath });
251
+ const pcErrors = validateArtifactPostconditions({
252
+ contract,
253
+ artifactPath: absPath,
254
+ cwd: repoRoot,
255
+ rootDir: repoRoot,
256
+ });
248
257
  for (const e of pcErrors) errors.push(e);
249
258
  }
250
259
 
@@ -397,7 +406,7 @@ function claimsHaveCitations(body) {
397
406
  * disk. Postconditions can be strings (descriptive, ignored here) or objects
398
407
  * with { id, check, field?, section? }. Returns an array of error strings.
399
408
  */
400
- export function validateArtifactPostconditions({ contract, artifactPath }) {
409
+ export function validateArtifactPostconditions({ contract, artifactPath, cwd = process.cwd(), rootDir } = {}) {
401
410
  if (!contract?.postconditions?.length) return [];
402
411
  if (!artifactPath || !existsSync(artifactPath)) {
403
412
  const structured = contract.postconditions.filter((p) => typeof p === 'object');
@@ -473,6 +482,26 @@ export function validateArtifactPostconditions({ contract, artifactPath }) {
473
482
  }
474
483
  break;
475
484
  }
485
+ case 'artifact-reviewers-seen': {
486
+ let missing;
487
+ if (pc.fromManifest) {
488
+ missing = missingRequiredReviewers({ filePath: artifactPath, cwd, rootDir });
489
+ } else {
490
+ missing = missingRequiredReviewers({
491
+ docType: pc.docType || inferArtifactTypeFromPath(artifactPath, { rootDir: cwd }),
492
+ filePath: artifactPath,
493
+ cwd,
494
+ rootDir,
495
+ });
496
+ }
497
+ if (Array.isArray(pc.reviewers) && pc.reviewers.length > 0) {
498
+ missing = missing.filter((r) => pc.reviewers.includes(r));
499
+ }
500
+ if (missing.length > 0) {
501
+ errors.push(`[${tag}] required reviewers not in agent log: ${missing.join(', ')}`);
502
+ }
503
+ break;
504
+ }
476
505
  default:
477
506
  errors.push(`[${tag}] unknown postcondition check: ${pc.check}`);
478
507
  }
@@ -23,7 +23,7 @@
23
23
  * verifyChain; all writers go through logViolation.
24
24
  */
25
25
 
26
- import { existsSync, mkdirSync, readFileSync } from 'node:fs';
26
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
27
27
  import { join, dirname } from 'node:path';
28
28
  import { homedir } from 'node:os';
29
29
  import { createHash } from 'node:crypto';
@@ -55,6 +55,51 @@ function logFile(repoRoot) {
55
55
  // log writes from the developer's project log.
56
56
  export function violationLogPath(repoRoot) { return logFile(repoRoot); }
57
57
 
58
+ function supersedeMarkerFile(repoRoot) {
59
+ if (repoRoot) return join(repoRoot, '.cx', 'contract-violations-superseded.json');
60
+ return resolveProjectScopedPath('contract-violations-superseded.json', { ensureDir: false });
61
+ }
62
+
63
+ /**
64
+ * Read ISO timestamp before which violations are ignored for oracle/doctor counts.
65
+ */
66
+ export function readViolationSupersedeCutoff(repoRoot) {
67
+ const file = supersedeMarkerFile(repoRoot);
68
+ if (!existsSync(file)) return null;
69
+ try {
70
+ const data = JSON.parse(readFileSync(file, 'utf8'));
71
+ const ts = data?.supersededBefore;
72
+ const parsed = Date.parse(String(ts ?? ''));
73
+ return Number.isFinite(parsed) ? parsed : null;
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Mark pre-fix contract violations as superseded for hygiene surfaces.
81
+ * Forensic log is preserved; oracle read model ignores older entries.
82
+ */
83
+ export function markContractViolationsSuperseded({ repoRoot, reason = 'superseded by contract hygiene repair' } = {}) {
84
+ const file = supersedeMarkerFile(repoRoot);
85
+ mkdirSync(dirname(file), { recursive: true });
86
+ const payload = {
87
+ supersededBefore: new Date().toISOString(),
88
+ reason,
89
+ };
90
+ writeFileSync(file, `${JSON.stringify(payload, null, 2)}\n`);
91
+ return payload;
92
+ }
93
+
94
+ function violationMatchesTail(tail, contractId, direction, missing, packet) {
95
+ if (!tail) return false;
96
+ if (tail.contractId !== contractId || tail.direction !== direction) return false;
97
+ const keys = packet && typeof packet === 'object' ? Object.keys(packet).sort() : null;
98
+ const tailKeys = Array.isArray(tail.packet_keys) ? [...tail.packet_keys].sort() : null;
99
+ return JSON.stringify(tail.missing) === JSON.stringify(missing)
100
+ && JSON.stringify(tailKeys) === JSON.stringify(keys);
101
+ }
102
+
58
103
  function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
59
104
 
60
105
  function readLastAgent() {
@@ -91,6 +136,8 @@ function nextSequence(repoRoot) {
91
136
  export function logViolation(contractId, direction, missing, packet, extra = {}) {
92
137
  try {
93
138
  const { repoRoot, ...persistedExtra } = extra || {};
139
+ const tail = readTailRecord(repoRoot);
140
+ if (violationMatchesTail(tail, contractId, direction, missing, packet)) return;
94
141
  const file = logFile(repoRoot);
95
142
  mkdirSync(dirname(file), { recursive: true });
96
143
  const record = {
@@ -113,17 +160,23 @@ export function logViolation(contractId, direction, missing, packet, extra = {})
113
160
  * Rotated segments are intentionally not included — this is the doctor
114
161
  * surface, scoped to the active window.
115
162
  */
116
- export function recentViolations({ windowMs = 24 * 60 * 60 * 1000 } = {}) {
117
- const file = logFile();
163
+ export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot } = {}) {
164
+ const file = logFile(repoRoot);
118
165
  if (!existsSync(file)) return [];
166
+ const supersedeCutoff = readViolationSupersedeCutoff(repoRoot);
119
167
  try {
120
- const cutoff = Date.now() - windowMs;
168
+ const windowCutoff = Date.now() - windowMs;
121
169
  return readFileSync(file, 'utf8')
122
170
  .trim()
123
171
  .split('\n')
124
172
  .filter(Boolean)
125
173
  .map((line) => { try { return JSON.parse(line); } catch { return null; } })
126
- .filter((r) => r && new Date(r.ts).getTime() >= cutoff);
174
+ .filter((r) => {
175
+ if (!r) return false;
176
+ const ts = new Date(r.ts).getTime();
177
+ if (supersedeCutoff != null && ts <= supersedeCutoff) return false;
178
+ return ts >= windowCutoff;
179
+ });
127
180
  } catch { return []; }
128
181
  }
129
182
 
@@ -0,0 +1,71 @@
1
+ /**
2
+ * lib/dashboard-demo.mjs — Construct cockpit Playwright demo adapter.
3
+ *
4
+ * Resolves dashboard recording manifests (or legacy script bridge) and delegates
5
+ * to lib/playwright-demo.mjs with dashboard workspace defaults.
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import { loadDemoRecording } from './demo-recording.mjs';
10
+ import {
11
+ detectPlaywrightDemo,
12
+ recordPlaywrightDemo,
13
+ collectVideoFiles,
14
+ newestVideo,
15
+ finalizeDemoVideo,
16
+ locateFfmpeg,
17
+ transcodeWebmToMp4,
18
+ resolvePlaywrightPackage,
19
+ } from './playwright-demo.mjs';
20
+
21
+ export {
22
+ collectVideoFiles,
23
+ newestVideo,
24
+ finalizeDemoVideo,
25
+ locateFfmpeg,
26
+ transcodeWebmToMp4,
27
+ resolvePlaywrightPackage,
28
+ };
29
+
30
+ export function dashboardWorkspaceRoot(repoRoot) {
31
+ return path.join(repoRoot, 'apps', 'dashboard');
32
+ }
33
+
34
+ export function detectDashboardDemo({ cwd = process.cwd(), repoRoot } = {}) {
35
+ const root = repoRoot || cwd;
36
+ return detectPlaywrightDemo({ workspace: dashboardWorkspaceRoot(root), repoRoot: root, cwd });
37
+ }
38
+
39
+ export function recordDashboardDemo(name, {
40
+ cwd = process.cwd(),
41
+ repoRoot,
42
+ outputDir,
43
+ outputPath = null,
44
+ format = 'mp4',
45
+ artifactDir = null,
46
+ artifactFile = null,
47
+ env = process.env,
48
+ } = {}) {
49
+ const root = repoRoot || cwd;
50
+ const recording = loadDemoRecording(name, { cwd, repoRoot: root });
51
+
52
+ if (!recording) {
53
+ const detection = detectDashboardDemo({ cwd, repoRoot: root });
54
+ if (!detection.present) {
55
+ return { ok: false, name, missing: detection.missing, message: detection.message };
56
+ }
57
+ return { ok: false, name, message: `No recording manifest or dashboard demo script for ${name}` };
58
+ }
59
+
60
+ const outDir = outputDir || path.join(cwd, '.cx', 'demos', 'dashboard');
61
+ return recordPlaywrightDemo(recording, {
62
+ cwd,
63
+ repoRoot: root,
64
+ outputDir: outDir,
65
+ outputPath,
66
+ format,
67
+ artifactDir,
68
+ artifactFile,
69
+ env,
70
+ });
71
+ }
@@ -11,6 +11,7 @@
11
11
  import fs from 'node:fs';
12
12
  import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
+ import { sanitizeNpmSpawnEnv } from './npm-spawn-env.mjs';
14
15
 
15
16
  function walkFiles(rootDir, currentDir = rootDir) {
16
17
  if (!fs.existsSync(currentDir)) return [];
@@ -125,11 +126,24 @@ export function buildDashboardStatic({
125
126
  spawnSyncFn = spawnSync,
126
127
  } = {}) {
127
128
  const { dashboardDir } = getDashboardPaths({ rootDir });
128
- const result = spawnSyncFn('npm', ['run', 'build'], {
129
- cwd: dashboardDir,
130
- encoding: 'utf8',
131
- stdio: ['ignore', 'pipe', 'pipe'],
132
- });
129
+ const nextCache = path.join(dashboardDir, '.next');
130
+ fs.rmSync(nextCache, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
131
+
132
+ const env = sanitizeNpmSpawnEnv(process.env);
133
+ let result;
134
+ for (let attempt = 1; attempt <= 2; attempt += 1) {
135
+ result = spawnSyncFn('npm', ['--prefix', dashboardDir, 'run', 'build'], {
136
+ cwd: rootDir,
137
+ encoding: 'utf8',
138
+ stdio: ['ignore', 'pipe', 'pipe'],
139
+ env,
140
+ });
141
+ if (result.status === 0) break;
142
+ const output = `${result.stdout || ''}\n${result.stderr || ''}`;
143
+ const transientRename = /ENOENT.*rename.*\.next\/(export|server\/pages)\/500\.html/.test(output);
144
+ if (!transientRename) break;
145
+ fs.rmSync(nextCache, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
146
+ }
133
147
  if (result.status !== 0) {
134
148
  throw new Error((result.stderr || result.stdout || 'dashboard build failed').trim());
135
149
  }
@@ -23,6 +23,8 @@ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
23
23
  import { join, dirname, resolve, relative } from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
25
 
26
+ import { readRuleFile } from '../rules-read.mjs';
27
+
26
28
  export const DECISION_STATUSES = ['proposed', 'accepted', 'superseded', 'deprecated'];
27
29
 
28
30
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
@@ -35,7 +37,7 @@ const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
35
37
  const ENFORCES_LINE_RE = /^(?:\/\/|#|\*)?\s*@enforces\s+(\S+)\s*$/;
36
38
 
37
39
  // The body carries Status and Supersedes as markdown definition lines, not
38
- // frontmatter (see docs/adr/_template.md); parse them positionally.
40
+ // frontmatter (see docs/adr/templates/_template.md); parse them positionally.
39
41
 
40
42
  // ADR field styles vary across the lane: inline bold (`- **Status**: x`,
41
43
  // `**Status:** X`) and heading-then-value (`## Status` followed by the value on
@@ -184,9 +186,9 @@ function ruleDecisions({ repoRoot, bindings }) {
184
186
  const dir = join(repoRoot, 'rules');
185
187
  const out = [];
186
188
  for (const file of walk(dir, (f) => f.endsWith('.md'))) {
187
- const text = readFileSync(file, 'utf8');
188
- const fm = parseFrontmatter(text);
189
189
  const rel = relative(repoRoot, file);
190
+ const text = readRuleFile(rel, { rootDir: repoRoot, source: 'validation', callerContext: 'decisions-registry' });
191
+ const fm = parseFrontmatter(text);
190
192
  const id = `rule:${relative(join(repoRoot, 'rules'), file).replace(/\.md$/, '')}`;
191
193
  const enforcedBy = fm.enforced_by ? fm.enforced_by.split(',').map((s) => s.trim()).filter(Boolean) : [];
192
194
  const enforcingTests = bindings.get(id) || [];