@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,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
+ }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * lib/artifact-type-from-path.mjs — Infer manifest doc type from artifact file paths.
3
+ *
4
+ * Resolves a manifest doc type when --type is omitted.
5
+ * Resolution order: YAML frontmatter cx_doc_type → directory heuristics → filename hints.
6
+ */
7
+
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { artifactTypes } from './artifact-manifest.mjs';
11
+
12
+ const KNOWN = new Set(artifactTypes());
13
+
14
+ function readFrontmatterType(filePath) {
15
+ try {
16
+ const head = fs.readFileSync(filePath, 'utf8').slice(0, 4096);
17
+ const match = head.match(/^---\n([\s\S]*?)\n---/);
18
+ if (!match) return null;
19
+ for (const line of match[1].split('\n')) {
20
+ const m = line.match(/^(?:cx_doc_type|artifactType|doc_type)\s*:\s*(.+)$/);
21
+ if (m) {
22
+ const value = m[1].trim().replace(/^['"]|['"]$/g, '');
23
+ return KNOWN.has(value) ? value : null;
24
+ }
25
+ }
26
+ } catch { /* unreadable */ }
27
+ return null;
28
+ }
29
+
30
+ function inferPrdSubtype(rel, base) {
31
+ const blob = `${rel} ${base}`.toLowerCase();
32
+ if (/meta[\s-]?prd/.test(blob)) return 'meta-prd';
33
+ if (/platform/.test(blob)) return 'prd-platform';
34
+ if (/business/.test(blob)) return 'prd-business';
35
+ return 'prd';
36
+ }
37
+
38
+ function inferRfcSubtype(rel, base) {
39
+ const blob = `${rel} ${base}`.toLowerCase();
40
+ if (/platform/.test(blob)) return 'rfc-platform';
41
+ return 'rfc';
42
+ }
43
+
44
+ const PREFIX_RULES = [
45
+ [/^docs\/prd\//, inferPrdSubtype],
46
+ [/^docs\/adr\//, () => 'adr'],
47
+ [/^docs\/rfc\//, inferRfcSubtype],
48
+ [/^docs\/research\//, () => 'research-brief'],
49
+ [/^docs\/runbooks?\//, () => 'runbook'],
50
+ [/^\.cx\/research\//, () => 'research-brief'],
51
+ ];
52
+
53
+ export function isArtifactGatePath(relPath) {
54
+ const rel = relPath.replace(/\\/g, '/');
55
+ if (!rel.endsWith('.md')) return false;
56
+ if (rel.startsWith('templates/docs/')) return false;
57
+ if (/^docs\//.test(rel) || /^\.cx\/research\//.test(rel)) return true;
58
+ return false;
59
+ }
60
+
61
+ export function inferArtifactTypeFromPath(filePath, { rootDir = process.cwd() } = {}) {
62
+ const rel = path.relative(rootDir, filePath).replace(/\\/g, '/');
63
+ if (!rel.endsWith('.md')) return null;
64
+
65
+ const fromFm = readFrontmatterType(filePath);
66
+ if (fromFm) return fromFm;
67
+
68
+ if (!isArtifactGatePath(rel)) return null;
69
+
70
+ const base = path.basename(filePath, path.extname(filePath));
71
+ for (const [re, resolver] of PREFIX_RULES) {
72
+ if (re.test(rel)) {
73
+ const type = typeof resolver === 'function' ? resolver(rel, base) : resolver;
74
+ return KNOWN.has(type) ? type : null;
75
+ }
76
+ }
77
+
78
+ return null;
79
+ }
@@ -0,0 +1,252 @@
1
+ /**
2
+ * lib/artifact-workflow.mjs — intent-to-artifact workflow planning.
3
+ *
4
+ * Planning describes specialist work and manifest ordering without reporting a
5
+ * planned role as executed.
6
+ */
7
+
8
+ import { existsSync } from 'node:fs';
9
+ import { loadProjectConfig } from './config/project-config.mjs';
10
+ import {
11
+ BRAND_CAPABLE_FORMATS,
12
+ artifactTypes,
13
+ resolveArtifactType,
14
+ resolveArtifactWorkflowContract,
15
+ } from './artifact-manifest.mjs';
16
+ import { inferArtifactTypeFromPath } from './artifact-type-from-path.mjs';
17
+ import { validateArtifactRelease } from './artifact-release-gate.mjs';
18
+ import { exportMarkdown } from './document-export.mjs';
19
+
20
+ function escapeRegex(value) {
21
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
+ }
23
+
24
+ function findRequestedTypes(input, { rootDir, cwd }) {
25
+ const text = String(input ?? '').toLowerCase();
26
+ const matches = [];
27
+ for (const type of artifactTypes({ rootDir, cwd }).sort((a, b) => b.length - a.length)) {
28
+ const entry = resolveArtifactType(type, { rootDir, cwd }).entry;
29
+ for (const candidate of [type, ...(entry?.aliases ?? [])]) {
30
+ if (new RegExp(`(?:^|[^a-z0-9])${escapeRegex(candidate.toLowerCase())}(?:$|[^a-z0-9])`, 'i').test(text)) {
31
+ matches.push(type);
32
+ break;
33
+ }
34
+ }
35
+ }
36
+ return [...new Set(matches)];
37
+ }
38
+
39
+ function inferFormat(input, requestedFormat) {
40
+ if (requestedFormat) return String(requestedFormat).toLowerCase();
41
+ const text = String(input ?? '').toLowerCase();
42
+ for (const format of ['pptx', 'docx', 'pdf', 'deck', 'html', 'doc', 'rtf', 'odt', 'epub', 'tex', 'txt', 'mdx', 'md']) {
43
+ if (new RegExp(`\\b${format}\\b`, 'i').test(text)) return format;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function inferBranding(input, requestedBranding) {
49
+ if (requestedBranding) return requestedBranding;
50
+ return /\b(?:unbranded|plain|without (?:the )?construct brand)\b/i.test(String(input ?? '')) ? 'plain' : 'construct';
51
+ }
52
+
53
+ function buildSteps({ contract, input, targetFormat, branding }) {
54
+ const text = String(input ?? '').toLowerCase();
55
+ const needsResearch = Boolean(contract.researchProfile) && /\b(?:research|source|evidence|validate claims)\b/.test(text);
56
+ const needsReview = /\b(?:review|critique|audit|feedback)\b/.test(text) || contract.requiredReviewers.length > 0;
57
+ const rewrite = /\b(?:rewrite|revise|edit|polish|improve)\b/.test(text);
58
+ const steps = [];
59
+ if (needsResearch) steps.push({ id: 'research', action: 'research', roles: [], required: false, reason: `research profile: ${contract.researchProfile}` });
60
+ if (needsReview) steps.push({ id: 'review', action: 'review', roles: contract.reviewerChain, required: contract.requiredReviewers.length > 0, reason: 'review occurs before any rewrite' });
61
+ steps.push({
62
+ id: rewrite ? 'rewrite' : 'author',
63
+ action: rewrite ? 'rewrite' : 'author',
64
+ roles: contract.authorChain,
65
+ required: true,
66
+ reason: rewrite ? 'requested revision after review' : 'manifest author chain',
67
+ });
68
+ if (contract.validation.releaseGate !== false) {
69
+ steps.push({ id: 'validate', action: 'validate', roles: [], required: true, reason: 'manifest validation policy' });
70
+ }
71
+ if (targetFormat && BRAND_CAPABLE_FORMATS.includes(targetFormat) && branding === 'construct') {
72
+ steps.push({ id: 'brand', action: 'brand', roles: [], required: true, reason: 'default Construct branding for brand-capable format' });
73
+ }
74
+ if (targetFormat) steps.push({ id: 'export', action: 'export', roles: [], required: true, format: targetFormat, reason: 'requested distribution format' });
75
+ return steps;
76
+ }
77
+
78
+ /**
79
+ * Build an artifact plan from an ordinary request. A missing or ambiguous
80
+ * class is a clarification result, never an implicit PRD route.
81
+ */
82
+ export function planArtifactWorkflow(request = {}, { cwd = process.cwd(), rootDir } = {}) {
83
+ const input = request.input ?? request.request ?? '';
84
+ const sourcePath = request.sourcePath ?? request.filePath ?? '';
85
+ const project = request.projectConfig ?? loadProjectConfig(cwd).config;
86
+ const requested = request.artifactType ?? request.documentClass ?? null;
87
+ const pathType = sourcePath && existsSync(sourcePath) ? inferArtifactTypeFromPath(sourcePath, { rootDir: cwd }) : null;
88
+ const inferredTypes = requested ? [] : findRequestedTypes(input, { rootDir, cwd });
89
+ const candidates = requested ? [requested] : [...new Set([pathType, ...inferredTypes].filter(Boolean))];
90
+
91
+ if (candidates.length !== 1) {
92
+ return {
93
+ kind: 'artifact-workflow',
94
+ status: 'needs-classification',
95
+ plannedSteps: [],
96
+ executedSteps: [],
97
+ skippedSteps: [],
98
+ producedFiles: [],
99
+ validation: null,
100
+ appliedOverrides: [],
101
+ clarification: candidates.length > 1
102
+ ? `Request names multiple document classes (${candidates.join(', ')}). Specify one registered class.`
103
+ : 'Specify a registered document class, use a typed source path, or register the requested class in specialists/artifact-manifest.json.',
104
+ };
105
+ }
106
+
107
+ const resolution = resolveArtifactType(candidates[0], { rootDir, cwd });
108
+ if (resolution.status !== 'registered') {
109
+ return {
110
+ kind: 'artifact-workflow',
111
+ status: 'needs-classification',
112
+ plannedSteps: [],
113
+ executedSteps: [],
114
+ skippedSteps: [],
115
+ producedFiles: [],
116
+ validation: null,
117
+ appliedOverrides: [],
118
+ clarification: resolution.guidance,
119
+ };
120
+ }
121
+
122
+ const targetFormat = inferFormat(input, request.format ?? request.targetFormat);
123
+ const requestedBranding = inferBranding(input, request.branding);
124
+ const contract = resolveArtifactWorkflowContract(resolution.type, {
125
+ rootDir,
126
+ cwd,
127
+ projectConfig: project,
128
+ overrides: request.overrides,
129
+ });
130
+ const branding = request.branding ?? (requestedBranding === 'plain' ? 'plain' : (contract.outputs.branding ?? 'construct'));
131
+ const steps = buildSteps({ contract, input, targetFormat, branding });
132
+ const unavailableFormat = targetFormat && !contract.outputs.formats.includes(targetFormat);
133
+
134
+ return {
135
+ kind: 'artifact-workflow',
136
+ status: unavailableFormat ? 'needs-classification' : 'planned',
137
+ documentClass: contract.documentClass,
138
+ artifactType: contract.type,
139
+ sourcePath: sourcePath || null,
140
+ target: {
141
+ format: targetFormat,
142
+ audience: /\bcustomer(?:-facing)?\b/i.test(input) ? 'customer' : 'internal',
143
+ branding,
144
+ brandCapable: Boolean(targetFormat && BRAND_CAPABLE_FORMATS.includes(targetFormat)),
145
+ },
146
+ contract,
147
+ plannedSteps: steps,
148
+ executedSteps: [],
149
+ skippedSteps: [],
150
+ producedFiles: [],
151
+ validation: null,
152
+ appliedOverrides: contract.appliedOverrides,
153
+ clarification: unavailableFormat ? `${targetFormat} is not an output capability for ${contract.type}.` : null,
154
+ };
155
+ }
156
+
157
+ /**
158
+ * Produce a provenance-preserving workflow run report. Specialist authoring
159
+ * and review are never fabricated: Construct can plan them, but only reports
160
+ * them as executed when a host returns execution evidence (not available in
161
+ * the deterministic entry point). Validation/export are local operations and
162
+ * run only with the explicit durable-write approval mode.
163
+ */
164
+ export function runArtifactWorkflow(request = {}, { cwd = process.cwd(), rootDir } = {}) {
165
+ const plan = planArtifactWorkflow(request, { cwd, rootDir });
166
+ const approvalMode = request.approvalMode ?? 'proposal-only';
167
+ const report = {
168
+ reportVersion: 1,
169
+ kind: 'artifact-workflow-run',
170
+ status: plan.status === 'planned' ? 'planned' : plan.status,
171
+ approval: {
172
+ mode: approvalMode,
173
+ durableWriteAllowed: approvalMode === 'allow-durable-write',
174
+ },
175
+ plan,
176
+ plannedSteps: plan.plannedSteps,
177
+ executedSteps: [],
178
+ skippedSteps: [],
179
+ producedFiles: [],
180
+ validation: null,
181
+ appliedOverrides: plan.appliedOverrides,
182
+ };
183
+ if (plan.status !== 'planned') return report;
184
+
185
+ const pendingReason = approvalMode === 'allow-durable-write'
186
+ ? 'specialist execution is host-owned; this local command has no specialist execution evidence'
187
+ : `approval mode '${approvalMode}' permits planning only`;
188
+ for (const step of plan.plannedSteps) {
189
+ if (['review', 'rewrite', 'author', 'research'].includes(step.action)) {
190
+ report.skippedSteps.push({ id: step.id, reason: pendingReason });
191
+ }
192
+ }
193
+ if (approvalMode !== 'allow-durable-write') {
194
+ for (const step of plan.plannedSteps) {
195
+ if (!['review', 'rewrite', 'author', 'research'].includes(step.action)) {
196
+ report.skippedSteps.push({ id: step.id, reason: pendingReason });
197
+ }
198
+ }
199
+ return report;
200
+ }
201
+
202
+ const sourcePath = plan.sourcePath;
203
+ for (const step of plan.plannedSteps) {
204
+ if (step.action === 'validate') {
205
+ if (!sourcePath) {
206
+ report.skippedSteps.push({ id: step.id, reason: 'validation needs sourcePath or filePath' });
207
+ } else {
208
+ const result = validateArtifactRelease({ filePath: sourcePath, type: plan.artifactType, cwd, rootDir });
209
+ report.validation = result;
210
+ report.executedSteps.push({ id: step.id, result: result.ok ? 'passed' : 'failed' });
211
+ if (!result.ok) {
212
+ report.status = 'validation-failed';
213
+ return report;
214
+ }
215
+ }
216
+ }
217
+ if (step.action === 'brand') {
218
+ // Branding is applied by the export operation. Keeping this pending until
219
+ // output exists avoids claiming a template was applied when no export ran.
220
+ report.skippedSteps.push({ id: step.id, reason: 'branding is evidenced by the export result' });
221
+ }
222
+ if (step.action === 'export') {
223
+ if (!sourcePath) {
224
+ report.skippedSteps.push({ id: step.id, reason: 'export needs sourcePath or filePath' });
225
+ } else {
226
+ const result = exportMarkdown({
227
+ inputPath: sourcePath,
228
+ outputPath: request.outputPath,
229
+ format: step.format,
230
+ artifactType: plan.artifactType,
231
+ branding: plan.target.branding,
232
+ cwd,
233
+ rootDir,
234
+ });
235
+ if (!result.ok) {
236
+ report.status = 'export-failed';
237
+ report.skippedSteps.push({ id: step.id, reason: result.message });
238
+ return report;
239
+ }
240
+ report.executedSteps.push({ id: step.id, result: 'produced', format: step.format });
241
+ report.producedFiles.push({ path: result.outputPath, format: step.format, branding: result.branding });
242
+ const brandStep = report.skippedSteps.findIndex((entry) => entry.id === 'brand');
243
+ if (brandStep >= 0 && result.branding?.applied === 'construct') {
244
+ report.skippedSteps.splice(brandStep, 1);
245
+ report.executedSteps.push({ id: 'brand', result: 'applied', mechanism: result.branding.mechanism });
246
+ }
247
+ }
248
+ }
249
+ }
250
+ report.status = report.executedSteps.length ? 'completed-local-steps' : 'planned';
251
+ return report;
252
+ }
@@ -155,6 +155,10 @@ export function auditSkills({ rootDir, silent = false } = {}) {
155
155
 
156
156
  export async function runAuditSkillsCli(args = []) {
157
157
  const rootDir = args.find((a) => a.startsWith('--root='))?.split('=')[1] ?? undefined;
158
+ if (args.includes('--inventory')) {
159
+ const { runSkillInventoryAuditCli } = await import('./certification/skill-inventory.mjs');
160
+ return runSkillInventoryAuditCli(args.filter((a) => a !== '--inventory'), { rootDir });
161
+ }
158
162
  const result = auditSkills({ rootDir });
159
163
  if (!result.pass) process.exit(1);
160
164
  }