@geraldmaron/construct 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  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 +112 -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/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
@@ -0,0 +1,103 @@
1
+ ---
2
+ description: Distribution example — operational runbook with diagnostic flow and severity table.
3
+ artifactType: runbook
4
+ subtitle: Publish pipeline degraded — Pandoc or figure tooling missing
5
+ version: "1.1"
6
+ doc_id: RUNBOOK-PUBLISH-001
7
+ classification: internal
8
+ status: accepted
9
+ owner: cx-sre
10
+ last_verified_at: 2026-06-22
11
+ ---
12
+
13
+ # Runbook: construct publish / export failures
14
+
15
+ - **Service**: document-export (`construct publish`, `construct export`)
16
+ - **Owner**: platform on-call
17
+ - **Last tested**: 2026-06-22
18
+ - **Severity**: SEV-2 when exports block release; SEV-3 when examples only
19
+
20
+ ## Alert trigger
21
+
22
+ `construct publish` exits 2 with `missing: pandoc` or `Figure tooling` in `construct tools detect --figures` output. CI job `release:check` fails on export certification scenarios.
23
+
24
+ ## Symptoms
25
+
26
+ - PDF export fails before Typst invocation
27
+ - `--figures` renders empty diagram placeholders
28
+ - `npm run examples:distribution` reports partial failures in summary
29
+
30
+ ## Impact
31
+
32
+ | Audience | Impact |
33
+ |---|---|
34
+ | Release managers | Cannot ship branded PRDs/ADRs |
35
+ | Developers | Local previews blocked |
36
+ | Agents | `publish_run` MCP tool returns remediation hints |
37
+
38
+ ## Severity and response
39
+
40
+ | Severity | Trigger condition | Page within | Comms | Error budget |
41
+ |----------|-------------------|-------------|-------|--------------|
42
+ | SEV-1 | All publish paths down in CI | 5 min | #incident + status | breach → freeze |
43
+ | SEV-2 | PDF down, HTML OK | 15 min | #platform | partial spend |
44
+ | SEV-3 | Examples script only | business hours | team channel | none |
45
+
46
+ ## Diagnostic steps
47
+
48
+ 1. Run `construct tools detect --figures --json` and read `steps.export` + `steps.figures`.
49
+ 2. Confirm `pandoc` and `typst` on PATH; confirm `d2` and `mmdc` when `--figures` is set.
50
+ 3. Re-run a single fixture: `node bin/construct export examples/distribution/sources/adr.md --to=pdf --figures`.
51
+
52
+ ```mermaid
53
+ flowchart TD
54
+ A[Alert fires] --> B{export.present?}
55
+ B -->|No| C[Install pandoc + typst]
56
+ B -->|Yes| D{figures.present?}
57
+ D -->|No| E[brew install d2; npm i -g @mermaid-js/mermaid-cli]
58
+ D -->|Yes| F[Re-run examples:distribution]
59
+ C --> F
60
+ E --> F
61
+ ```
62
+
63
+ ```d2
64
+ direction: down
65
+
66
+ detect: tools detect
67
+ pandoc: pandoc + typst
68
+ figures: d2 + mmdc
69
+ ok: Green export
70
+
71
+ detect -> pandoc: if missing
72
+ detect -> figures: if --figures
73
+ pandoc -> ok
74
+ figures -> ok
75
+ ```
76
+
77
+ ## Remediation
78
+
79
+ ```bash
80
+ brew install pandoc typst d2 graphviz
81
+ npm install -g @mermaid-js/mermaid-cli
82
+ npm run examples:distribution
83
+ open .tmp/distribution-examples/index.html
84
+ ```
85
+
86
+ Expected: index lists PDF and HTML links for each artifact type; no `FAILED` lines in script output.
87
+
88
+ ## Rollback
89
+
90
+ Not applicable — this runbook restores tooling, not application state. If a bad template change caused failures, revert the Typst commit and re-run exports.
91
+
92
+ ## Escalation
93
+
94
+ After 30 minutes without restore: page cx-platform-engineer. After 60 minutes with release blocked: page cx-release-manager.
95
+
96
+ ## Post-incident
97
+
98
+ Preserve `construct tools detect --json` output and the failing pandoc/typst stderr in the incident ticket.
99
+
100
+ ## References
101
+
102
+ - `docs/reference/document-io.md`
103
+ - `docs/cookbook/diagram-and-demo.md`
@@ -0,0 +1,88 @@
1
+ ---
2
+ description: Distribution example — strategy memo with goals, metrics, and landscape diagram.
3
+ artifactType: strategy
4
+ subtitle: Make Construct the default governed layer for agentic IDE workflows
5
+ version: "1.0"
6
+ doc_id: STRATEGY-2026-H1
7
+ classification: internal
8
+ status: draft
9
+ owner: cx-product-manager
10
+ last_verified_at: 2026-06-22
11
+ ---
12
+
13
+ # Strategy: Governed agentic workflows for IDE-native teams
14
+
15
+ - **Date**: 2026-06-22
16
+ - **Owner**: cx-product-manager
17
+ - **Status**: draft
18
+ - **Horizon**: H1 2026 – H1 2027
19
+
20
+ > Platform and product teams want specialist routing without losing citation discipline, branded exports, or profile-aware terminology. Construct wins when evaluators can open one gallery of PDFs and see the same ink, typography, and diagram styling across document types.
21
+
22
+ ## Problem
23
+
24
+ Host-native agents optimize for single-session chat. Enterprise platform teams need durable invoke plans, manifest-enforced artifacts, and distribution that looks intentional — not markdown attachments with host fonts.
25
+
26
+ ## Goals
27
+
28
+ 1. **Prove value in five minutes** — `npm run examples:distribution` produces a browsable gallery with figures.
29
+ 2. **One brand contract** — `docs/reference/branding.md` + `03d-brand` audit ratchet.
30
+ 3. **Profile-aware language** — intake queue labels follow active profile rebrand in CLI, session prelude, and dashboard API.
31
+
32
+ ## Non-goals
33
+
34
+ Replacing host LLM execution. Mandating cloud render farms. Re-linting every historical doc for marketing voice.
35
+
36
+ ## Landscape
37
+
38
+ ```mermaid
39
+ flowchart TB
40
+ subgraph hosts [IDE hosts]
41
+ CC[Claude Code]
42
+ OC[OpenCode]
43
+ CX[Cursor]
44
+ end
45
+ subgraph construct [Construct]
46
+ R[Router]
47
+ V[Validate]
48
+ P[Publish]
49
+ end
50
+ hosts --> R --> V --> P
51
+ P --> PDF[Branded PDF]
52
+ P --> HTML[HTML / deck]
53
+ ```
54
+
55
+ ```d2
56
+ direction: down
57
+
58
+ construct: Construct {
59
+ style.fill: "#f3f4f6"
60
+ }
61
+
62
+ hosts: IDE hosts
63
+ validate: Release gate
64
+ export: Branded export
65
+
66
+ hosts -> construct -> validate -> export
67
+ ```
68
+
69
+ ## Success metrics
70
+
71
+ | Metric | Baseline | Target |
72
+ |---|---|---|
73
+ | Brand audit findings | unknown | 0 in `03d-brand` |
74
+ | Example types with figures | 1 (PRD) | ≥6 layouts |
75
+ | Dashboard rebrand coverage | partial | intake API + pages |
76
+
77
+ ## Risks and mitigations
78
+
79
+ | Risk | Mitigation |
80
+ |---|---|
81
+ | Toolchain missing on contributor laptops | `construct tools detect --figures` in runbook |
82
+ | Template drift | Functional tests on `construct-brand.typ` + HTML decks |
83
+ | Profile label regressions | `intake-rebrand.functional.test.mjs` |
84
+
85
+ ## References
86
+
87
+ - `examples/distribution/manifest.json`
88
+ - https://langchain-ai.github.io/langgraph/ (accessed 2026-06-22)
@@ -0,0 +1,67 @@
1
+ /**
2
+ * lib/adapters-sync.mjs — project adapter sync for the Construct tool repo and
3
+ * `npm run adapters`. Detects installed hosts, runs sync-specialists --project,
4
+ * and is invoked from postinstall when developing inside the package itself.
5
+ */
6
+
7
+ import { spawnSync } from 'node:child_process';
8
+ import { existsSync } from 'node:fs';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+
12
+ import { detectHostCapabilities } from './host-capabilities.mjs';
13
+ import { isConstructPackageRepo } from './host-disposition.mjs';
14
+ import { stageProjectAdapters } from './install/stage-project.mjs';
15
+
16
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
17
+ const PKG_ROOT = path.resolve(MODULE_DIR, '..');
18
+
19
+ const HOST_ID_MAP = {
20
+ 'Claude Code': 'claude',
21
+ OpenCode: 'opencode',
22
+ Codex: 'codex',
23
+ 'VS Code': 'vscode',
24
+ Cursor: 'cursor',
25
+ Copilot: 'copilot',
26
+ };
27
+
28
+ export function resolveAdapterHosts({ forceAll = false, extra = [] } = {}) {
29
+ if (forceAll) return ['claude', 'opencode', 'codex', 'vscode', 'cursor'];
30
+ const hosts = new Set(extra);
31
+ for (const entry of detectHostCapabilities()) {
32
+ if (entry.availability !== 'installed') continue;
33
+ const id = HOST_ID_MAP[entry.host];
34
+ if (id) hosts.add(id);
35
+ }
36
+ if (hosts.size === 0) hosts.add('claude');
37
+ return [...hosts];
38
+ }
39
+
40
+ export function syncProjectAdapters({
41
+ projectRoot = process.cwd(),
42
+ packageRoot = PKG_ROOT,
43
+ hosts = null,
44
+ log = () => {},
45
+ } = {}) {
46
+ const resolvedHosts = hosts ?? resolveAdapterHosts({ forceAll: isConstructPackageRepo(projectRoot) });
47
+ return stageProjectAdapters({
48
+ projectRoot,
49
+ packageRoot,
50
+ pkgVersion: null,
51
+ log,
52
+ hosts: resolvedHosts,
53
+ });
54
+ }
55
+
56
+ export function runAdaptersScript({ cwd = process.cwd(), hosts = null } = {}) {
57
+ const result = syncProjectAdapters({ projectRoot: cwd, hosts, log: (m) => process.stdout.write(`[adapters] ${m}\n`) });
58
+ return result.synced ? 0 : 1;
59
+ }
60
+
61
+ if (import.meta.url === `file://${process.argv[1]}`) {
62
+ const args = new Set(process.argv.slice(2));
63
+ const forceAll = args.has('--all-hosts');
64
+ const hosts = forceAll ? resolveAdapterHosts({ forceAll: true }) : null;
65
+ const code = runAdaptersScript({ cwd: process.cwd(), hosts });
66
+ process.exit(code);
67
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * lib/artifact-gate-notice.mjs — Shared PostToolUse artifact structure/visual check.
3
+ *
4
+ * Returns violations for typed docs under docs/** and .cx/research/** without
5
+ * applying TTY/CI suppression (callers decide whether to emit).
6
+ */
7
+
8
+ import path from 'node:path';
9
+ import { inferArtifactTypeFromPath, isArtifactGatePath } from './artifact-type-from-path.mjs';
10
+ import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
11
+
12
+ export function checkArtifactGateNotice(filePath, { cwd = process.cwd() } = {}) {
13
+ const rel = filePath.startsWith(cwd)
14
+ ? filePath.slice(cwd.length).replace(/^\/+/, '')
15
+ : filePath;
16
+ if (!isArtifactGatePath(rel)) return null;
17
+
18
+ const type = inferArtifactTypeFromPath(filePath, { rootDir: cwd });
19
+ if (!type) return null;
20
+
21
+ const errors = [
22
+ ...lintDocStructure(filePath, type),
23
+ ...lintDocVisuals(filePath, type),
24
+ ];
25
+ if (errors.length === 0) return null;
26
+
27
+ return { type, rel, errors };
28
+ }
29
+
30
+ export function formatArtifactGateNotice({ type, rel, errors }) {
31
+ const lines = [
32
+ `\nartifact-release-gate (${type}): ${errors.length} structure/visual issue(s). `
33
+ + 'Fix before calling the artifact done; full gate: '
34
+ + `\`construct artifact validate ${rel} --type=${type}\`\n`,
35
+ ];
36
+ for (const e of errors) lines.push(` - ${e}\n`);
37
+ return lines.join('');
38
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * lib/artifact-manifest.mjs — Load and resolve the artifact capability manifest.
3
+ *
4
+ * specialists/artifact-manifest.json is the single source of truth for document
5
+ * type metadata: templates, tone, structure, visuals, and release gates.
6
+ * Consumer projects without a local copy resolve the shipped manifest from the
7
+ * Construct package root.
8
+ */
9
+
10
+ import fs from 'node:fs';
11
+ import path from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+
14
+ const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
15
+ const EMPTY_MANIFEST = { version: 1, artifacts: {} };
16
+
17
+ let cached = null;
18
+ let cachedRoot = null;
19
+
20
+ function manifestPathForRoot(root) {
21
+ return path.join(root, 'specialists', 'artifact-manifest.json');
22
+ }
23
+
24
+ export function findConstructRoot(startPath = process.cwd()) {
25
+ let current = path.resolve(startPath);
26
+ while (true) {
27
+ const manifest = manifestPathForRoot(current);
28
+ if (fs.existsSync(manifest)) return current;
29
+ const parent = path.dirname(current);
30
+ if (parent === current) break;
31
+ current = parent;
32
+ }
33
+ if (fs.existsSync(manifestPathForRoot(PACKAGE_ROOT))) return PACKAGE_ROOT;
34
+ return null;
35
+ }
36
+
37
+ export function loadArtifactManifest({ rootDir, force = false, cwd = process.cwd() } = {}) {
38
+ const resolvedRoot = rootDir ?? findConstructRoot(cwd) ?? PACKAGE_ROOT;
39
+ if (cached && !force && cachedRoot === resolvedRoot) return cached;
40
+
41
+ const p = manifestPathForRoot(resolvedRoot);
42
+ if (!fs.existsSync(p)) {
43
+ cached = EMPTY_MANIFEST;
44
+ cachedRoot = resolvedRoot;
45
+ return cached;
46
+ }
47
+
48
+ cached = JSON.parse(fs.readFileSync(p, 'utf8'));
49
+ cachedRoot = resolvedRoot;
50
+ return cached;
51
+ }
52
+
53
+ export function getArtifactEntry(type, opts = {}) {
54
+ const manifest = loadArtifactManifest(opts);
55
+ return manifest.artifacts?.[type] ?? null;
56
+ }
57
+
58
+ export function artifactTypes(opts = {}) {
59
+ const manifest = loadArtifactManifest(opts);
60
+ return Object.keys(manifest.artifacts ?? {});
61
+ }
62
+
63
+ export function structureRequirementsFromManifest(opts = {}) {
64
+ const manifest = loadArtifactManifest(opts);
65
+ const out = {};
66
+ for (const [type, entry] of Object.entries(manifest.artifacts ?? {})) {
67
+ if (entry.structureRequirements?.length) out[type] = entry.structureRequirements;
68
+ }
69
+ return out;
70
+ }
71
+
72
+ export function visualRequirementsFromManifest(opts = {}) {
73
+ const manifest = loadArtifactManifest(opts);
74
+ const out = {};
75
+ for (const [type, entry] of Object.entries(manifest.artifacts ?? {})) {
76
+ if (entry.visualRequirements?.length) out[type] = entry.visualRequirements;
77
+ }
78
+ return out;
79
+ }
80
+
81
+ export function loadBrandVoice(cwd = process.cwd()) {
82
+ const p = path.join(cwd, '.cx', 'brand-voice.json');
83
+ if (!fs.existsSync(p)) return null;
84
+ try {
85
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+
91
+ export function resolveToneForArtifact(type, { cwd = process.cwd(), rootDir } = {}) {
92
+ const entry = getArtifactEntry(type, { rootDir, cwd });
93
+ const brand = loadBrandVoice(cwd);
94
+ const override = brand?.toneOverride?.[type];
95
+ if (override) return override;
96
+ return entry?.toneDefault ?? 'direct';
97
+ }
98
+
99
+ export function templateMetadata(type, { cwd = process.cwd(), rootDir } = {}) {
100
+ const entry = getArtifactEntry(type, { rootDir, cwd });
101
+ if (!entry) return null;
102
+ return {
103
+ type,
104
+ tone: resolveToneForArtifact(type, { cwd, rootDir }),
105
+ toneAllowed: entry.toneAllowed ?? [],
106
+ structureRequirements: entry.structureRequirements ?? [],
107
+ visualRequirements: entry.visualRequirements ?? [],
108
+ primaryOwners: entry.primaryOwners ?? [],
109
+ workflowSkill: entry.workflowSkill ?? null,
110
+ releaseGate: entry.releaseGate ?? null,
111
+ };
112
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * lib/artifact-release-gate.mjs — Real-org release validation for typed artifacts.
3
+ *
4
+ * Composes structure/visual lint, citation discipline, prose heuristics, and
5
+ * optional reviewer sign-off from the artifact manifest releaseGate block.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { lintDocStructure, lintDocVisuals } from './templates/visual-requirements.mjs';
11
+ import { getArtifactEntry, resolveToneForArtifact } from './artifact-manifest.mjs';
12
+ import { lintFile } from './comment-lint.mjs';
13
+ import {
14
+ missingRequiredReviewers,
15
+ parseReleaseGateFrontmatter,
16
+ } from './artifact-reviewers.mjs';
17
+ import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
18
+
19
+ function countProseParagraphs(body) {
20
+ const blocks = body.split(/\n\s*\n/).map((b) => b.trim()).filter(Boolean);
21
+ let prose = 0;
22
+ for (const b of blocks) {
23
+ const first = b.split('\n')[0].trimStart();
24
+ const isHeading = first.startsWith('#');
25
+ const isList = /^([-*+]\s|\d+[.)]\s|>\s)/.test(first);
26
+ const isTable = first.startsWith('|');
27
+ const isFence = first.startsWith('```');
28
+ if (isHeading || isList || isTable || isFence) continue;
29
+ const sentences = (b.match(/[.!?](\s|$)/g) || []).length;
30
+ if (sentences >= 2 || b.length >= 200) prose++;
31
+ }
32
+ return prose;
33
+ }
34
+
35
+ function countCitations(body) {
36
+ const patterns = [
37
+ /\bhttps?:\/\/[^\s)]+/g,
38
+ /\barxiv:\s*\d{4}\.\d{4,5}/gi,
39
+ /\[source:[^\]]+\]/gi,
40
+ /\(accessed\s+\d{4}-\d{2}-\d{2}\)/gi,
41
+ ];
42
+ const hits = new Set();
43
+ for (const re of patterns) for (const m of body.match(re) || []) hits.add(m.trim().toLowerCase());
44
+ return hits.size;
45
+ }
46
+
47
+ export function validateArtifactRelease({
48
+ filePath,
49
+ type,
50
+ cwd = process.cwd(),
51
+ rootDir,
52
+ reviewersSeen,
53
+ } = {}) {
54
+ const resolvedType = type || inferArtifactTypeFromPath(filePath, { rootDir: cwd });
55
+ const entry = getArtifactEntry(resolvedType, { rootDir });
56
+ if (!entry) {
57
+ return { ok: false, errors: [`Unknown artifact type: ${resolvedType}`], warnings: [], tone: null };
58
+ }
59
+
60
+ const { bypass, reason } = parseReleaseGateFrontmatter(filePath);
61
+ if (bypass) {
62
+ if (!reason) {
63
+ return {
64
+ ok: false,
65
+ errors: ['cx_release_gate: bypass requires cx_release_gate_reason in frontmatter'],
66
+ warnings: [],
67
+ tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
68
+ type: resolvedType,
69
+ filePath,
70
+ bypassed: true,
71
+ };
72
+ }
73
+ return {
74
+ ok: true,
75
+ errors: [],
76
+ warnings: [`release gate bypassed: ${reason}`],
77
+ tone: resolveToneForArtifact(resolvedType, { cwd, rootDir }),
78
+ type: resolvedType,
79
+ filePath,
80
+ bypassed: true,
81
+ };
82
+ }
83
+
84
+ const gate = entry.releaseGate ?? {};
85
+ const errors = [];
86
+ const warnings = [];
87
+ const raw = fs.readFileSync(filePath, 'utf8');
88
+ const body = raw.replace(/^---\n[\s\S]*?\n---\n/, '');
89
+
90
+ if (gate.structuralLint !== false) {
91
+ errors.push(...lintDocStructure(filePath, resolvedType));
92
+ errors.push(...lintDocVisuals(filePath, resolvedType));
93
+ }
94
+
95
+ if (gate.citationLint) {
96
+ const isShippedTemplate = filePath.includes(`${path.sep}templates${path.sep}docs${path.sep}`);
97
+ if (!isShippedTemplate) {
98
+ const lint = lintFile(filePath);
99
+ const artifactIssues = [...(lint.errors ?? []), ...(lint.warnings ?? [])].filter((w) => w.kind === 'artifact');
100
+ for (const issue of artifactIssues) errors.push(issue.message || String(issue));
101
+ const citations = countCitations(body);
102
+ const unverified = /\[unverified\]/i.test(body);
103
+ if (citations < 1 && !unverified) {
104
+ errors.push('citationLint: no verifiable sources or [unverified] markers');
105
+ }
106
+ }
107
+ }
108
+
109
+ const minProse = gate.proseMinimum ?? 0;
110
+ if (minProse > 0) {
111
+ const prose = countProseParagraphs(body);
112
+ if (prose < minProse) {
113
+ errors.push(`proseMinimum: ${prose} paragraphs (need ${minProse})`);
114
+ }
115
+ }
116
+
117
+ const missing = missingRequiredReviewers({
118
+ docType: resolvedType,
119
+ filePath,
120
+ rootDir,
121
+ cwd,
122
+ reviewersSeen,
123
+ });
124
+ if (missing.length > 0) {
125
+ warnings.push(`requiredReviewers not seen in agent log: ${missing.join(', ')}`);
126
+ }
127
+
128
+ const tone = resolveToneForArtifact(resolvedType, { cwd, rootDir });
129
+
130
+ return {
131
+ ok: errors.length === 0,
132
+ errors,
133
+ warnings,
134
+ tone,
135
+ type: resolvedType,
136
+ filePath,
137
+ };
138
+ }
139
+
140
+ export async function runArtifactValidateCli(args = []) {
141
+ const positional = args.filter((a) => !a.startsWith('--'));
142
+ const filePath = positional[0];
143
+ const typeArg = args.find((a) => a.startsWith('--type='));
144
+ const json = args.includes('--json');
145
+
146
+ if (!filePath || !typeArg) {
147
+ process.stderr.write('Usage: construct artifact validate <path> --type=<doc-type> [--json]\n');
148
+ process.exit(1);
149
+ }
150
+
151
+ const type = typeArg.split('=')[1];
152
+ const result = validateArtifactRelease({ filePath, type });
153
+
154
+ if (json) {
155
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
156
+ } else {
157
+ process.stdout.write(`Artifact release gate: ${type}\n`);
158
+ process.stdout.write(` Tone: ${result.tone}\n`);
159
+ if (result.errors.length) {
160
+ process.stdout.write(` Errors (${result.errors.length}):\n`);
161
+ for (const e of result.errors) process.stdout.write(` - ${e}\n`);
162
+ }
163
+ if (result.warnings.length) {
164
+ process.stdout.write(` Warnings (${result.warnings.length}):\n`);
165
+ for (const w of result.warnings) process.stdout.write(` - ${w}\n`);
166
+ }
167
+ process.stdout.write(result.ok ? ' Result: PASS\n' : ' Result: FAIL\n');
168
+ }
169
+
170
+ if (!result.ok) process.exit(1);
171
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * lib/artifact-reviewers.mjs — Required reviewer resolution from manifest + agent log.
3
+ *
4
+ * Shared by artifact release gate, contract postconditions, and Oracle read-model.
5
+ */
6
+
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { getArtifactEntry } from './artifact-manifest.mjs';
10
+ import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
11
+
12
+ const RELEASE_GATE_BYPASS_FIELD = 'cx_release_gate';
13
+ const RELEASE_GATE_REASON_FIELD = 'cx_release_gate_reason';
14
+
15
+ export function readAgentLogReviewers(projectRoot = process.cwd()) {
16
+ const logPath = path.join(projectRoot, '.cx', 'agent-log.jsonl');
17
+ if (!fs.existsSync(logPath)) return new Set();
18
+ const reviewers = new Set();
19
+ for (const line of fs.readFileSync(logPath, 'utf8').split('\n')) {
20
+ if (!line.trim()) continue;
21
+ try {
22
+ const row = JSON.parse(line);
23
+ const id = row.agent || row.specialist;
24
+ if (id) reviewers.add(String(id));
25
+ } catch { /* skip */ }
26
+ }
27
+ return reviewers;
28
+ }
29
+
30
+ export function parseReleaseGateFrontmatter(filePath) {
31
+ try {
32
+ const head = fs.readFileSync(filePath, 'utf8').slice(0, 4096);
33
+ const match = head.match(/^---\n([\s\S]*?)\n---/);
34
+ if (!match) return { bypass: false, reason: null };
35
+ const fm = {};
36
+ for (const line of match[1].split('\n')) {
37
+ const m = line.match(/^([\w-]+)\s*:\s*(.+)$/);
38
+ if (m) fm[m[1]] = m[2].trim().replace(/^['"]|['"]$/g, '');
39
+ }
40
+ const bypass = fm[RELEASE_GATE_BYPASS_FIELD] === 'bypass';
41
+ return { bypass, reason: fm[RELEASE_GATE_REASON_FIELD] ?? null };
42
+ } catch {
43
+ return { bypass: false, reason: null };
44
+ }
45
+ }
46
+
47
+ export function resolveRequiredReviewers({ docType, filePath, rootDir, cwd = process.cwd() }) {
48
+ let type = docType;
49
+ if (!type && filePath) {
50
+ type = inferArtifactTypeFromPath(filePath, { rootDir: cwd });
51
+ }
52
+ if (!type) return [];
53
+ const entry = getArtifactEntry(type, { rootDir });
54
+ return entry?.releaseGate?.requiredReviewers ?? [];
55
+ }
56
+
57
+ export function missingRequiredReviewers({
58
+ docType,
59
+ filePath,
60
+ rootDir,
61
+ cwd = process.cwd(),
62
+ reviewersSeen,
63
+ } = {}) {
64
+ const required = resolveRequiredReviewers({ docType, filePath, rootDir, cwd });
65
+ if (required.length === 0) return [];
66
+ const seen = reviewersSeen ?? readAgentLogReviewers(cwd);
67
+ return required.filter((r) => !seen.has(r) && !seen.has(r.replace(/^cx-/, 'cx-')));
68
+ }