@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
package/bin/construct CHANGED
@@ -12,10 +12,10 @@ import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
14
 
15
- import { CLI_COMMANDS, CLI_COMMANDS_BY_CATEGORY, CATEGORY_ORDER, formatCommandHelp, isInternalCommand } from '../lib/cli-commands.mjs';
15
+ import { CLI_COMMANDS, CATEGORY_ORDER, formatCommandHelp } from '../lib/cli-commands.mjs';
16
16
  import { buildStatus, formatStatusReport } from '../lib/status.mjs';
17
17
  import { validateRegistry } from '../lib/validator.mjs';
18
- import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, applyToEnv, resetEnv, setTierModel, setModelWithTierInference } from '../lib/model-router.mjs';
18
+ import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, applyToEnv, resetEnv, setModelWithTierInference } from '../lib/model-router.mjs';
19
19
  import { pollFreeModels, topForTier, selectForTier } from '../lib/model-free-selector.mjs';
20
20
  import { cmdMcpList, cmdMcpAdd, cmdMcpRemove, cmdMcpInfo } from '../lib/mcp-manager.mjs';
21
21
  import { cmdOllama } from '../lib/ollama-manager.mjs';
@@ -26,7 +26,8 @@ import { runUpdate } from '../lib/update.mjs';
26
26
  import { runUpgrade } from '../lib/upgrade.mjs';
27
27
  import { runUninstall } from '../lib/uninstall/uninstall.mjs';
28
28
  import { maybeFirstInvocationProbe } from '../lib/install/first-invocation.mjs';
29
- import { loadConstructEnv, getUserEnvPath, writeEnvValues, ensureUserConfigDir } from '../lib/env-config.mjs';
29
+ import { loadConstructEnv, getUserEnvPath } from '../lib/env-config.mjs';
30
+ import { ensureConstructCredentials } from '../lib/providers/credential-bootstrap.mjs';
30
31
  import {
31
32
  loadProjectConfig,
32
33
  writeProjectConfig,
@@ -39,30 +40,30 @@ import {
39
40
  import { validateProjectConfig } from '../lib/config/schema.mjs';
40
41
  import {
41
42
  DEPLOYMENT_MODES,
42
- DEFAULT_DEPLOYMENT_MODE,
43
43
  DEPLOYMENT_MODE_ENV_KEY,
44
44
  describeDeploymentMode,
45
- describeResourceLine,
46
45
  getDeploymentMode,
47
- isValidDeploymentMode,
48
46
  resolveResourceMode,
49
47
  } from '../lib/deployment-mode.mjs';
50
48
  import { runDistillCli } from '../lib/distill.mjs';
51
49
  import { runIngestCli } from '../lib/document-ingest.mjs';
52
50
  import { runInferCli } from '../lib/schema-infer.mjs';
53
- import { COMPLETIONS_DIR, generateCompletions, getCompletionScript } from '../lib/completions.mjs';
51
+ import { generateCompletions, getCompletionScript } from '../lib/completions.mjs';
54
52
  import { runHeadhunt, listTeamTemplates } from '../lib/headhunt.mjs';
55
53
  import {
56
54
  getLockStatus,
57
55
  cleanupStaleLock,
58
56
  cleanupStaleQueue,
59
57
  readQueue,
60
- formatStatus,
61
58
  } from '../lib/beads-lock.mjs';
62
59
  import { runBd, getHumanStatus, acquireMergeSlot, releaseMergeSlot } from '../lib/beads-client.mjs';
63
60
  import { auditSkills, runAuditSkillsCli } from '../lib/audit-skills.mjs';
61
+ import { auditSpecialists, runAuditSpecialistsCli } from '../lib/audit-specialists.mjs';
62
+ import { runCapabilityLedgerAuditCli } from '../lib/capability-ledger.mjs';
63
+ import { runCorpusInventoryAuditCli } from '../lib/test-corpus-inventory.mjs';
64
+ import { runArtifactValidateCli } from '../lib/artifact-release-gate.mjs';
64
65
  import { runTeamReviewCli } from '../lib/telemetry/team-rollup.mjs';
65
- import { readDashboardState, startDashboard, startServices } from '../lib/service-manager.mjs';
66
+ import { startDashboard, startServices } from '../lib/service-manager.mjs';
66
67
  import { readCostLog, summarizeCostData, formatCostReport, clearCostLog } from '../lib/cost.mjs';
67
68
  import { readEfficiencyLog, summarizeEfficiencyData, formatEfficiencyReport } from '../lib/efficiency.mjs';
68
69
  import { resolveColors } from '../lib/term-format.mjs';
@@ -74,6 +75,7 @@ import { runPressureRelease } from '../lib/runtime-pressure.mjs';
74
75
 
75
76
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
76
77
  const HOME = os.homedir();
78
+ ensureConstructCredentials({ env: process.env, cwd: ROOT_DIR, home: HOME });
77
79
  const ENV = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: HOME, env: process.env });
78
80
  for (const [key, value] of Object.entries(ENV)) {
79
81
  if (!(key in process.env)) process.env[key] = value;
@@ -170,41 +172,51 @@ function runNodeScript(scriptPath, args = [], extraEnv = {}, { exitOnError = tru
170
172
  }
171
173
 
172
174
  async function cmdRegistryStatus(args = []) {
173
- const { readFileSync, existsSync } = await import('node:fs');
174
- const { join } = await import('node:path');
175
- const matrixPath = join(ROOT_DIR, 'tests', 'registry', 'capability-matrix.json');
176
-
177
- if (!existsSync(matrixPath)) {
178
- errorln('Capability matrix not found. Run "construct sync" or ensure tests/registry/capability-matrix.json exists.');
179
- process.exit(1);
180
- }
175
+ const { runRegistryStatus } = await import('../lib/registry/cli.mjs');
176
+ const code = await runRegistryStatus(args, { rootDir: ROOT_DIR, println, errorln });
177
+ if (code) process.exit(code);
178
+ }
181
179
 
182
- const { capabilities } = JSON.parse(readFileSync(matrixPath, 'utf8'));
183
- const jsonOutput = args.includes('--json');
180
+ async function cmdRegistryValidate(args = []) {
181
+ const { runRegistryValidate } = await import('../lib/registry/cli.mjs');
182
+ const code = await runRegistryValidate(args, { rootDir: ROOT_DIR, println, errorln });
183
+ if (code) process.exit(code);
184
+ }
184
185
 
185
- if (jsonOutput) {
186
- println(JSON.stringify(capabilities, null, 2));
187
- return;
188
- }
186
+ async function cmdRegistryGenerateDocs(args = []) {
187
+ const { runRegistryGenerateDocs } = await import('../lib/registry/cli.mjs');
188
+ const code = await runRegistryGenerateDocs(args, { rootDir: ROOT_DIR, println, errorln });
189
+ if (code) process.exit(code);
190
+ }
189
191
 
190
- println(`${COLORS.bold}Workflow & Surface Capability Registry${COLORS.reset}`);
191
- println('='.repeat(40));
192
- println('');
192
+ async function cmdRulesUsage(args = []) {
193
+ const { summarizeRuleCalls } = await import('../lib/telemetry/rule-calls.mjs');
194
+ const { summarizeHookCalls } = await import('../lib/telemetry/hook-calls.mjs');
195
+ const sub = args[0] || 'usage';
196
+ const sinceFlag = args.find((a) => a.startsWith('--since='));
197
+ const since = sinceFlag ? sinceFlag.split('=')[1] : '30d';
193
198
 
194
- for (const cap of capabilities) {
195
- const tierColor = cap.criticality === 'P0' ? COLORS.red : cap.criticality === 'P1' ? COLORS.yellow : COLORS.blue;
196
- println(`${tierColor}[${cap.criticality}]${COLORS.reset} ${COLORS.bold}${cap.name}${COLORS.reset} (${cap.id})`);
197
- println(` ${COLORS.dim}${cap.description}${COLORS.reset}`);
198
-
199
- const surfaces = Object.entries(cap.surfaces);
200
- for (const [name, status] of surfaces) {
201
- const icon = status.quality_score === null ? '⚪' : status.quality_score >= 0.9 ? '🟢' : status.quality_score >= 0.7 ? '🟡' : '🔴';
202
- const score = status.quality_score !== null ? `${(status.quality_score * 100).toFixed(0)}%` : 'N/A';
203
- const date = status.last_validated ? new Date(status.last_validated).toLocaleDateString() : 'never';
204
- println(` ${icon} ${name.padEnd(10)} | Score: ${score.padEnd(5)} | Validated: ${date}`);
205
- }
199
+ if (sub === 'usage') {
200
+ const rules = summarizeRuleCalls({ since });
201
+ const hooks = summarizeHookCalls({ since });
202
+ println(`Rule references — last ${since} (${rules.total} events)\n`);
203
+ const ruleRows = Object.entries(rules.rules).sort((a, b) => b[1] - a[1]);
204
+ if (ruleRows.length === 0) println(' No rule reference events in the window.');
205
+ for (const [path, count] of ruleRows) println(` ${path.padEnd(50)} refs=${count}`);
206
206
  println('');
207
+ println(`Hook fires — last ${since} (${hooks.totalEvents ?? 0} events)\n`);
208
+ const hookEntries = Object.entries(hooks.hooks ?? {}).sort((a, b) => b[1].calls - a[1].calls);
209
+ if (!hookEntries.length) {
210
+ println(' No hook events in window.');
211
+ } else {
212
+ for (const [id, s] of hookEntries) {
213
+ println(` ${id.padEnd(40)} calls=${s.calls} blocked=${s.blocked ?? 0} errors=${s.errors ?? 0}`);
214
+ }
215
+ }
216
+ return;
207
217
  }
218
+ errorln(`Unknown rules subcommand: ${sub}. Available: usage`);
219
+ process.exit(1);
208
220
  }
209
221
 
210
222
  async function cmdStatus() {
@@ -255,22 +267,6 @@ async function cmdStatus() {
255
267
  } catch { /* best-effort surface; never block status */ }
256
268
  }
257
269
 
258
- async function cmdShow() {
259
- const status = await buildStatus({ rootDir: ROOT_DIR, cwd: process.cwd(), homeDir: HOME, env: process.env });
260
- const dashboard = readDashboardState(HOME);
261
- println('');
262
- println('Construct Services');
263
- println('══════════════════');
264
- println('');
265
- if (dashboard) println(` • Managed dashboard PID ${dashboard.pid} on ${dashboard.url}`);
266
- for (const service of status.system.services) {
267
- const icon = service.status === 'healthy' ? '✓' : service.status === 'degraded' ? '⚠' : '✗';
268
- const note = service.note ? ` (${service.note})` : '';
269
- println(` ${icon} ${service.name.padEnd(28)} ${service.url}${note}`);
270
- }
271
- println('');
272
- }
273
-
274
270
  async function cmdSync(args) {
275
271
  const { startOpLog } = await import('../lib/op-log.mjs');
276
272
  const opLog = startOpLog('sync', { homeDir: HOME });
@@ -504,15 +500,19 @@ async function cmdDoctor() {
504
500
 
505
501
  const checks = [];
506
502
  const add = (label, pass, optional = false) => checks.push({ label, pass, optional });
503
+ const { isConstructSourceCheckout } = await import('../lib/doctor/source-checkout.mjs');
504
+ const isSourceCheckout = isConstructSourceCheckout(ROOT_DIR);
507
505
  add('registry.json exists', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'registry.json')));
508
506
  add('sync-specialists.mjs exists', fs.existsSync(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs')));
509
507
 
510
- // Dashboard static build output. The Next.js app under apps/dashboard
511
- // emits a static export into lib/server/static/ via its postbuild script;
512
- // an empty static dir means lib/server/index.mjs will serve 404s.
508
+ // Dashboard static build output is absent from published npm installs.
513
509
 
514
- const dashboardIndex = path.join(ROOT_DIR, 'lib', 'server', 'static', 'index.html');
515
- add('Dashboard built (lib/server/static/index.html)', fs.existsSync(dashboardIndex));
510
+ if (isSourceCheckout) {
511
+ const dashboardIndex = path.join(ROOT_DIR, 'lib', 'server', 'static', 'index.html');
512
+ add('Dashboard built (lib/server/static/index.html)', fs.existsSync(dashboardIndex));
513
+ } else {
514
+ add('Dashboard built (skipped — published package)', true, true);
515
+ }
516
516
 
517
517
  // Project .gitignore must cover .cx/. Auto-fix via --fix-gitignore.
518
518
  // Only checks when cwd looks like a Construct project (has .cx/ or
@@ -677,6 +677,14 @@ async function cmdDoctor() {
677
677
 
678
678
  add('Node.js 20+ (recommended)', Number.parseInt(process.versions.node.split('.')[0], 10) >= 20);
679
679
  add('npm available', true);
680
+ const npmDevdir = process.env.npm_config_devdir ?? process.env.NPM_CONFIG_DEVDIR;
681
+ if (npmDevdir) {
682
+ add(
683
+ 'npm env: npm_config_devdir set (Cursor sandbox) — npm 11.2+ warns on every `npm run`. Unset: unset npm_config_devdir NPM_CONFIG_DEVDIR. Or run scripts directly: node scripts/... / construct ...',
684
+ false,
685
+ true,
686
+ );
687
+ }
680
688
  const constructOnPath = spawnSync('zsh', ['-lc', 'command -v construct'], { encoding: 'utf8', env: process.env });
681
689
  const constructPath = constructOnPath.status === 0 ? constructOnPath.stdout.trim() : '';
682
690
  const constructVersion = constructPath
@@ -854,6 +862,21 @@ async function cmdDoctor() {
854
862
  add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
855
863
  const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
856
864
  add('No declared skills missing on disk', skillAudit.pass, false);
865
+ if (isSourceCheckout) {
866
+ const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
867
+ add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
868
+
869
+ // The catalog is the public-service source of truth; doctor joins it to runtime dispatch
870
+ // and the generated reference so a command that is unroutable or undocumented surfaces here.
871
+
872
+ const { buildCliServiceInventory } = await import('../lib/cli-service-inventory.mjs');
873
+ const cliDrift = buildCliServiceInventory({ rootDir: ROOT_DIR })
874
+ .filter((entry) => !entry.runnable || !entry.documented);
875
+ add('Public CLI dispatch/reference parity', cliDrift.length === 0, false);
876
+ } else {
877
+ add('Specialist/skill audit cross-checks (skipped — published package)', true, true);
878
+ add('Public CLI dispatch/reference parity (skipped — published package)', true, true);
879
+ }
857
880
  const { lintResearchRepo } = await import('../lib/research-lint.mjs');
858
881
  const researchLintResults = lintResearchRepo({ rootDir: process.cwd() });
859
882
  add('Research artifacts meet minimum evidence structure', researchLintResults.every((entry) => entry.errors.length === 0), true);
@@ -916,6 +939,14 @@ async function cmdDoctor() {
916
939
  add('Cross-surface adapter parity', false);
917
940
  }
918
941
 
942
+ try {
943
+ const { checkProjectAdaptersForDoctor } = await import('../lib/doctor/project-adapters.mjs');
944
+ const projectAdapters = checkProjectAdaptersForDoctor({ rootDir: ROOT_DIR, projectDir: process.cwd() });
945
+ add(projectAdapters.label, projectAdapters.ok, projectAdapters.warning);
946
+ } catch {
947
+ add('Project adapters', false, true);
948
+ }
949
+
919
950
  try {
920
951
  const { validateSkills } = await import('../lib/validators/skills.mjs');
921
952
  const skillsRoot = path.join(ROOT_DIR, 'skills');
@@ -928,6 +959,28 @@ async function cmdDoctor() {
928
959
  add('Skill structure', false, true);
929
960
  }
930
961
 
962
+ try {
963
+ const { lintWorkflowSkillVerification } = await import('../lib/registry/skill-verification.mjs');
964
+ const wf = lintWorkflowSkillVerification(path.join(ROOT_DIR, 'skills'));
965
+ const wfLabel = wf.warnings.length
966
+ ? `Workflow skill verification (${wf.checked} checked, ${wf.warnings.length} missing bars)`
967
+ : `Workflow skill verification (${wf.checked} checked)`;
968
+ add(wfLabel, wf.valid && wf.warnings.length === 0, true);
969
+ } catch {
970
+ add('Workflow skill verification', false, true);
971
+ }
972
+
973
+ try {
974
+ const { validateCapabilityRegistry } = await import('../lib/registry/validate.mjs');
975
+ const reg = validateCapabilityRegistry({ rootDir: ROOT_DIR });
976
+ const regLabel = reg.warnings.length
977
+ ? `Capability registry (${reg.count} entries, ${reg.warnings.length} warnings)`
978
+ : `Capability registry (${reg.count} entries)`;
979
+ add(regLabel, reg.valid, reg.errors.length > 0);
980
+ } catch {
981
+ add('Capability registry', false, true);
982
+ }
983
+
931
984
  try {
932
985
  const { describeBreakers, STATES } = await import('../lib/providers/circuit-breaker.mjs');
933
986
  const breakers = describeBreakers();
@@ -1036,7 +1089,7 @@ async function cmdDoctor() {
1036
1089
  }
1037
1090
 
1038
1091
  // Agent contracts integrity
1039
- const { getAllContracts, summarize } = await import('../lib/specialist-contracts.mjs');
1092
+ const { getAllContracts } = await import('../lib/specialist-contracts.mjs');
1040
1093
  const contracts = getAllContracts();
1041
1094
  add('Agent contracts loaded', contracts.length > 0);
1042
1095
  if (contracts.length > 0) {
@@ -1242,7 +1295,7 @@ async function cmdDev(args = []) {
1242
1295
  opLog.event('selection', { services: selected ? [...selected] : 'all' });
1243
1296
 
1244
1297
  // Auto-magic health check — catch missing prerequisites before starting services
1245
- const { quickHealthCheck, detectCredentials, resolveCredentials } = await import('../lib/health-check.mjs');
1298
+ const { quickHealthCheck, resolveCredentials } = await import('../lib/health-check.mjs');
1246
1299
 
1247
1300
  // Resolve credentials from env, .env files, and 1Password before starting services
1248
1301
  const resolvedCreds = await resolveCredentials({ homeDir: HOME });
@@ -1524,17 +1577,19 @@ async function cmdExport(args) {
1524
1577
  const toFlag = args.find((a) => a.startsWith('--to='))?.split('=')[1];
1525
1578
  const outFlag = args.find((a) => a.startsWith('--output='))?.split('=')[1];
1526
1579
  const detectOnly = args.includes('--detect');
1580
+ const figures = !args.includes('--no-figures');
1581
+ const branding = args.includes('--plain') || args.includes('--no-brand') ? 'plain' : 'construct';
1527
1582
 
1528
1583
  const { detect, exportMarkdown, EXPORT_FORMATS } = await import('../lib/document-export.mjs');
1529
1584
 
1530
1585
  if (detectOnly) {
1531
1586
  const format = toFlag || 'pdf';
1532
- println(JSON.stringify(detect(format), null, 2));
1587
+ println(JSON.stringify(detect(format, process.env, { branding }), null, 2));
1533
1588
  return;
1534
1589
  }
1535
1590
 
1536
1591
  if (!inputPath) {
1537
- errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--detect]');
1592
+ errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--figures|--no-figures] [--plain] [--detect]');
1538
1593
  process.exit(1);
1539
1594
  }
1540
1595
  if (!toFlag) {
@@ -1544,7 +1599,14 @@ async function cmdExport(args) {
1544
1599
 
1545
1600
  const resolvedIn = path.resolve(process.cwd(), inputPath);
1546
1601
  const resolvedOut = outFlag ? path.resolve(process.cwd(), outFlag) : null;
1547
- const result = exportMarkdown({ inputPath: resolvedIn, outputPath: resolvedOut, format: toFlag });
1602
+ const result = exportMarkdown({
1603
+ inputPath: resolvedIn,
1604
+ outputPath: resolvedOut,
1605
+ format: toFlag,
1606
+ figures: toFlag === 'pdf' ? figures : false,
1607
+ branding,
1608
+ repoRoot: ROOT_DIR,
1609
+ });
1548
1610
  if (!result.ok) {
1549
1611
  errorln(result.message);
1550
1612
  process.exit(result.missing?.length ? 2 : 1);
@@ -1552,6 +1614,36 @@ async function cmdExport(args) {
1552
1614
  ok(result.message);
1553
1615
  }
1554
1616
 
1617
+ async function cmdPublish(args) {
1618
+ const { runPublishCli } = await import('../lib/publish.mjs');
1619
+ const { exitCode } = await runPublishCli(args, { cwd: process.cwd(), repoRoot: ROOT_DIR });
1620
+ process.exit(exitCode ?? 0);
1621
+ }
1622
+
1623
+ async function cmdTools(args) {
1624
+ const [sub, ...rest] = args;
1625
+ if (sub !== 'detect') {
1626
+ errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME] [--dashboard-demo=NAME]');
1627
+ process.exit(1);
1628
+ }
1629
+ const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
1630
+ const json = rest.includes('--json');
1631
+ const figures = !rest.includes('--no-figures');
1632
+ const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
1633
+ const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
1634
+ const detection = detectPublishPipeline({
1635
+ format: 'pdf',
1636
+ includeFigures: figures,
1637
+ includeTerminalDemo: demos.length > 0,
1638
+ includeDashboardDemo: dashboardDemos.length > 0,
1639
+ cwd: process.cwd(),
1640
+ repoRoot: ROOT_DIR,
1641
+ });
1642
+ if (json) println(formatToolsDetectReport(detection, { json: true }));
1643
+ else println(formatToolsDetectReport(detection));
1644
+ process.exit(detection.present ? 0 : 2);
1645
+ }
1646
+
1555
1647
  async function cmdInfer(args) {
1556
1648
  try {
1557
1649
  const result = await runInferCli(args, { cwd: process.cwd() });
@@ -1667,7 +1759,7 @@ async function cmdPrune(args) {
1667
1759
  println(`.cx/ now at ${Math.round(after.totalCxBytes / 1024 / 1024)}MB / ${Math.round(after.totalCxCap / 1024 / 1024)}MB cap (${Math.round(after.totalCxUsageRatio * 100)}%).`);
1668
1760
  }
1669
1761
 
1670
- async function cmdCosts(args) {
1762
+ async function _cmdCosts(args) {
1671
1763
  const sub = args[0] || 'status';
1672
1764
  const {
1673
1765
  getDailySpend,
@@ -1882,7 +1974,7 @@ async function cmdOverrides(args) {
1882
1974
  process.exit(1);
1883
1975
  }
1884
1976
 
1885
- async function cmdPricing(args) {
1977
+ async function _cmdPricing(args) {
1886
1978
  const sub = args[0] || 'status';
1887
1979
  const {
1888
1980
  describePricingCatalogFreshness,
@@ -2080,7 +2172,7 @@ async function cmdEmbeddedTriage(args) {
2080
2172
  input = fs.readFileSync(0, 'utf8');
2081
2173
  }
2082
2174
  if (!input.trim() && !ingestion?.error) {
2083
- // This is an embedded-contract verb: an embedder that pipes empty/no input
2175
+ // Embedded-contract verb: an embedder that pipes empty/no input
2084
2176
  // must still get a parseable, versioned envelope — not a bare exit. Emit a
2085
2177
  // typed error envelope on stdout so the contract holds, and exit non-zero so
2086
2178
  // shell callers still see failure.
@@ -2764,12 +2856,25 @@ async function cmdIntakeConfig(args) {
2764
2856
  const cwd = process.cwd();
2765
2857
 
2766
2858
  const action = args[0];
2859
+ if (action === 'migrate') {
2860
+ const { migrateLegacyIntakeConfig } = await import('../lib/intake/intake-config.mjs');
2861
+ const result = migrateLegacyIntakeConfig(cwd);
2862
+ if (!result.migrated) {
2863
+ info(result.reason ?? 'nothing to migrate');
2864
+ return;
2865
+ }
2866
+ info('Migrated .cx/intake-config.json → construct.config.json intakePolicy');
2867
+ println(JSON.stringify(result.policy, null, 2));
2868
+ return;
2869
+ }
2870
+
2767
2871
  if (!action || action === 'show') {
2768
2872
  const cfg = loadIntakeConfig(cwd, process.env);
2769
2873
  const desc = describeIntakeDepth(cfg.maxDepth);
2770
2874
  println(`Intake config (root: ${cwd})`);
2771
2875
  println('');
2772
2876
  println(` includeProjectInbox: ${cfg.includeProjectInbox ? 'on' : 'off'} (.cx/inbox)`);
2877
+ println(` includeRootInbox: ${(cfg.includeRootInbox ?? cfg.includeArchetypeInbox) ? 'on' : 'off'} (inbox/)`);
2773
2878
  println(` includeDocsIntake: ${cfg.includeDocsIntake ? 'on' : 'off'} (docs/intake)`);
2774
2879
  println(` maxDepth: ${cfg.maxDepth} — ${desc.label}`);
2775
2880
  println(` ${desc.detail}`);
@@ -2804,6 +2909,9 @@ async function cmdIntakeConfig(args) {
2804
2909
  const docsFlag = tail.find((a) => a.startsWith('--include-docs-intake='));
2805
2910
  if (docsFlag) patch.includeDocsIntake = /^(on|true|1|yes)$/i.test(docsFlag.split('=')[1] || '');
2806
2911
 
2912
+ const rootFlag = tail.find((a) => a.startsWith('--include-root-inbox='));
2913
+ if (rootFlag) patch.includeRootInbox = /^(on|true|1|yes)$/i.test(rootFlag.split('=')[1] || '');
2914
+
2807
2915
  const addFlags = tail.filter((a) => a.startsWith('--add-dir='));
2808
2916
  const removeFlags = tail.filter((a) => a.startsWith('--remove-dir='));
2809
2917
 
@@ -2830,6 +2938,123 @@ async function cmdIntakeConfig(args) {
2830
2938
  }
2831
2939
  }
2832
2940
 
2941
+ async function cmdSources(args) {
2942
+ const {
2943
+ validateSourceTargets,
2944
+ normalizeConfigTarget,
2945
+ resolveEffectiveSourceTargetsFromConfig,
2946
+ legacyEnvSourceTargets,
2947
+ SOURCE_PROVIDERS,
2948
+ } = await import('../lib/config/source-targets.mjs');
2949
+ const {
2950
+ loadProjectConfig,
2951
+ writeProjectConfig,
2952
+ findProjectConfigPath,
2953
+ PROJECT_CONFIG_FILENAME,
2954
+ } = await import('../lib/config/project-config.mjs');
2955
+
2956
+ const cwd = process.cwd();
2957
+ const action = args[0] ?? 'list';
2958
+
2959
+ if (action === 'list' || action === 'show') {
2960
+ const { config, path: cfgPath } = loadProjectConfig(cwd, process.env);
2961
+ const configTargets = (config.sources?.targets ?? []).map(normalizeConfigTarget);
2962
+ const envTargets = legacyEnvSourceTargets(process.env);
2963
+ const effective = resolveEffectiveSourceTargetsFromConfig(config, process.env);
2964
+ println(`Source targets (config: ${cfgPath ?? 'default'})`);
2965
+ println('');
2966
+ if (configTargets.length === 0) println(' (no targets in construct.config.json)');
2967
+ for (const t of configTargets) {
2968
+ println(` ${t.id} · ${t.provider} · ${JSON.stringify(t.selector)}`);
2969
+ }
2970
+ if (envTargets.length) {
2971
+ println('');
2972
+ println('Legacy env targets (merged at runtime):');
2973
+ for (const t of envTargets) {
2974
+ println(` ${t.id} · ${t.provenance} · ${t.provider} · ${JSON.stringify(t.selector)}`);
2975
+ }
2976
+ }
2977
+ println('');
2978
+ println(`Effective targets: ${effective.length}`);
2979
+ return;
2980
+ }
2981
+
2982
+ if (action === 'validate') {
2983
+ const loaded = loadProjectConfig(cwd, process.env);
2984
+ const targetErrors = validateSourceTargets(loaded.raw?.sources?.targets ?? loaded.config?.sources?.targets ?? []);
2985
+ const all = [...(loaded.errors ?? []), ...targetErrors];
2986
+ if (!all.length) {
2987
+ info(`${loaded.path ?? PROJECT_CONFIG_FILENAME}: source targets valid`);
2988
+ return;
2989
+ }
2990
+ errorln(`Source target validation failed (${all.length}):`);
2991
+ for (const e of all) errorln(` - ${e}`);
2992
+ process.exit(1);
2993
+ }
2994
+
2995
+ if (action === 'add') {
2996
+ const provider = args[1];
2997
+ const id = args[2];
2998
+ const selectorRaw = args[3];
2999
+ if (!provider || !id || !selectorRaw) {
3000
+ errorln('Usage: construct sources add <provider> <id> <selector-json>');
3001
+ process.exit(1);
3002
+ }
3003
+ if (!SOURCE_PROVIDERS.includes(provider)) {
3004
+ errorln(`Unknown provider: ${provider}. Expected: ${SOURCE_PROVIDERS.join(', ')}`);
3005
+ process.exit(1);
3006
+ }
3007
+ let selector;
3008
+ try { selector = JSON.parse(selectorRaw); } catch (err) {
3009
+ errorln(`Invalid selector JSON: ${err.message}`);
3010
+ process.exit(1);
3011
+ }
3012
+ const target = normalizeConfigTarget({ id, provider, selector });
3013
+ const targetErrors = validateSourceTargets([target]);
3014
+ if (targetErrors.length) {
3015
+ for (const e of targetErrors) errorln(` - ${e}`);
3016
+ process.exit(1);
3017
+ }
3018
+ const cfgPath = findProjectConfigPath(cwd) || path.join(cwd, PROJECT_CONFIG_FILENAME);
3019
+ const { config } = loadProjectConfig(cwd, process.env);
3020
+ const existing = config.sources?.targets ?? [];
3021
+ if (existing.some((t) => t.id === id)) {
3022
+ errorln(`Target id already exists: ${id}`);
3023
+ process.exit(1);
3024
+ }
3025
+ const next = {
3026
+ ...config,
3027
+ sources: { ...(config.sources ?? {}), targets: [...existing, target] },
3028
+ };
3029
+ writeProjectConfig(cfgPath, next);
3030
+ info(`added source target ${id}`);
3031
+ return;
3032
+ }
3033
+
3034
+ if (action === 'remove') {
3035
+ const id = args[1];
3036
+ if (!id) {
3037
+ errorln('Usage: construct sources remove <id>');
3038
+ process.exit(1);
3039
+ }
3040
+ const cfgPath = findProjectConfigPath(cwd) || path.join(cwd, PROJECT_CONFIG_FILENAME);
3041
+ const { config } = loadProjectConfig(cwd, process.env);
3042
+ const existing = config.sources?.targets ?? [];
3043
+ const nextTargets = existing.filter((t) => t.id !== id);
3044
+ if (nextTargets.length === existing.length) {
3045
+ errorln(`No target with id: ${id}`);
3046
+ process.exit(1);
3047
+ }
3048
+ const next = { ...config, sources: { ...(config.sources ?? {}), targets: nextTargets } };
3049
+ writeProjectConfig(cfgPath, next);
3050
+ info(`removed source target ${id}`);
3051
+ return;
3052
+ }
3053
+
3054
+ errorln(`Unknown sources action: ${action}. Available: list, add, remove, validate`);
3055
+ process.exit(1);
3056
+ }
3057
+
2833
3058
  async function cmdIntakeMetrics() {
2834
3059
  try {
2835
3060
  const { computeIntakeMetrics, pendingAge } = await import('../lib/embed/intake-metrics.mjs');
@@ -3078,7 +3303,7 @@ async function cmdWorkspace(args) {
3078
3303
  const sub = args[0];
3079
3304
  if (!sub || sub === '--help' || sub === '-h') { printWorkspaceHelp(); return; }
3080
3305
 
3081
- const { createWorkspace, getWorkspace, listWorkspaces, updateWorkspace } = await import('../lib/embed/workspaces.mjs');
3306
+ const { createWorkspace, getWorkspace, listWorkspaces } = await import('../lib/embed/workspaces.mjs');
3082
3307
 
3083
3308
  if (sub === 'list') {
3084
3309
  const workspaces = listWorkspaces();
@@ -3192,7 +3417,6 @@ async function cmdRecommendations(args) {
3192
3417
  const reasonFlag = args.find(a => a.startsWith('--reason='))?.split('=').slice(1).join('=');
3193
3418
  const suppressDaysFlag = args.find(a => a.startsWith('--suppress-days='))?.split('=')[1];
3194
3419
  try {
3195
- const { dedupKey: computeKey } = await import('../lib/embed/recommendation-store.mjs');
3196
3420
  const key = `${recType}::${recTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 60)}`;
3197
3421
  const { dismissRecommendation } = await import('../lib/embed/recommendation-store.mjs');
3198
3422
  dismissRecommendation(key, {
@@ -3602,6 +3826,7 @@ async function cmdModels(args) {
3602
3826
  return;
3603
3827
  }
3604
3828
  if (tier && setModel) {
3829
+ const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3605
3830
  const preferFree = args.includes('--prefer-free');
3606
3831
  const preferFreeSameFamily = args.includes('--prefer-free-same-family');
3607
3832
  const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
@@ -3647,7 +3872,7 @@ async function cmdModels(args) {
3647
3872
  return;
3648
3873
  }
3649
3874
  if (args.includes('--cheapest')) {
3650
- const { selectCheapestProvider, rankConfiguredProvidersByCost, formatCheapestProviderMessage, isCheapestProviderEnabled } =
3875
+ const { selectCheapestProvider, formatCheapestProviderMessage, isCheapestProviderEnabled } =
3651
3876
  await import('../lib/model-cheapest-provider.mjs');
3652
3877
  const cheapestTier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? 'standard';
3653
3878
  const verbose = args.includes('--verbose');
@@ -3659,7 +3884,7 @@ async function cmdModels(args) {
3659
3884
  return;
3660
3885
  }
3661
3886
  if (args.includes('--apply-cheapest')) {
3662
- const { selectCheapestForAllTiers, setCheapestProviderPreference } =
3887
+ const { setCheapestProviderPreference } =
3663
3888
  await import('../lib/model-cheapest-provider.mjs');
3664
3889
  const allTiers = args.includes('--all-tiers') || !args.find((arg) => arg.startsWith('--tier='));
3665
3890
  const tierArg = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1];
@@ -3684,6 +3909,22 @@ async function cmdModels(args) {
3684
3909
  await cmdSync([]);
3685
3910
  return;
3686
3911
  }
3912
+ if (args.includes('--list') || args[0] === 'list') {
3913
+ const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
3914
+ const { listChatModels } = await import('../apps/chat/engine/models.mjs');
3915
+ const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
3916
+ const models = listChatModels({ env: process.env, cwd: process.cwd() });
3917
+ if (args.includes('--json')) {
3918
+ println(JSON.stringify({ catalog, models }, null, 2));
3919
+ return;
3920
+ }
3921
+ println('Visible models (filtered by construct.config.json models.visibility):');
3922
+ for (const model of models) {
3923
+ const flag = model.available ? 'ok' : '—';
3924
+ println(` ${flag} ${model.id}${model.configured ? '' : ' (provider not configured)'}`);
3925
+ }
3926
+ return;
3927
+ }
3687
3928
  const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3688
3929
  const result = readCurrentModels(envPath, registry.models ?? {});
3689
3930
  const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
@@ -3937,10 +4178,21 @@ async function cmdCleanup() {
3937
4178
  println(` ! ${err.step}: ${err.error}`);
3938
4179
  }
3939
4180
  }
4181
+
4182
+ // Reclaim orphaned Postgres containers/volumes from deleted home namespaces.
4183
+ const { reclaimOrphanedDockerResources, formatReclaim } = await import('../lib/maintenance/docker-reclaim.mjs');
4184
+ const reclaim = reclaimOrphanedDockerResources({ homeDir: HOME, env: process.env, dryRun });
4185
+ if (!quiet) {
4186
+ const line = formatReclaim(reclaim);
4187
+ if (line) println(` ${line}`);
4188
+ for (const entry of reclaim.errors ?? []) {
4189
+ println(` ! docker reclaim ${entry.name}: ${entry.error}`);
4190
+ }
4191
+ }
3940
4192
  }
3941
4193
  }
3942
4194
 
3943
- async function cmdCost(args) {
4195
+ async function _cmdCost(args) {
3944
4196
  const jsonOutput = args.includes('--json');
3945
4197
  const resetFlag = args.includes('--reset');
3946
4198
  const daysArg = args.find((a) => a.startsWith('--days='));
@@ -4191,8 +4443,21 @@ async function cmdDocsCheck(args) {
4191
4443
  }
4192
4444
  }
4193
4445
 
4194
- async function cmdDocsSite() {
4195
- const { buildFumadocsReference } = await import('../lib/auto-docs.mjs');
4446
+ async function cmdDocsSite(args = []) {
4447
+ const check = args.includes('--check');
4448
+ const { buildFumadocsReference, checkFumadocsReferenceDrift } = await import('../lib/auto-docs.mjs');
4449
+ if (check) {
4450
+ const { drift } = checkFumadocsReferenceDrift({ rootDir: ROOT_DIR });
4451
+ if (drift.length === 0) {
4452
+ ok('docs/reference/ is up to date.');
4453
+ return;
4454
+ }
4455
+ println('');
4456
+ println('Generated reference docs are out of date. Run `construct docs:site` to regenerate:');
4457
+ for (const f of drift) println(` ✗ ${f}`);
4458
+ process.exitCode = 1;
4459
+ return;
4460
+ }
4196
4461
  const { written } = buildFumadocsReference({ rootDir: ROOT_DIR });
4197
4462
  if (written.length === 0) {
4198
4463
  ok('docs/reference/ already up to date.');
@@ -4340,7 +4605,6 @@ async function cmdCiPreview(args) {
4340
4605
  const { readFileSync, existsSync } = await import('node:fs');
4341
4606
  const { join } = await import('node:path');
4342
4607
  const { spawnSync: spSync } = await import('node:child_process');
4343
- const YAML = await import('node:fs');
4344
4608
 
4345
4609
  const listOnly = args.includes('--list');
4346
4610
  const fullMode = args.includes('--full');
@@ -4838,7 +5102,7 @@ async function cmdTags(args) {
4838
5102
 
4839
5103
  async function cmdScheduler(args) {
4840
5104
  const sub = args[0];
4841
- const { registerJob, runJobOnce, listJobs } = await import('../lib/scheduler/index.mjs');
5105
+ const { runJobOnce, listJobs } = await import('../lib/scheduler/index.mjs');
4842
5106
 
4843
5107
  if (!sub || sub === 'list') {
4844
5108
  const jobs = listJobs();
@@ -4876,18 +5140,30 @@ async function cmdScheduler(args) {
4876
5140
 
4877
5141
  async function cmdCreds(args) {
4878
5142
  const sub = args[0];
4879
- const { readCreds, writeCreds, deleteCreds, listCreds, checkCredsFileMode, credsFilePath } =
5143
+ const { writeCreds, deleteCreds, listCreds, checkCredsFileMode, credsFilePath } =
4880
5144
  await import('../lib/providers/creds.mjs');
4881
5145
  const { createInterface } = await import('node:readline');
4882
5146
 
4883
5147
  if (!sub || sub === 'list') {
4884
5148
  const entries = listCreds();
4885
- if (entries.length === 0) { println('No provider credentials stored.'); return; }
4886
- println('Provider credentials\n');
4887
- println(' PROVIDER ACCOUNT ROTATED_AT NEXT_ROTATION');
4888
- for (const e of entries) {
4889
- println(` ${e.provider.padEnd(26)} ${(e.account || '—').padEnd(27)} ${(e.rotatedAt || '—').padEnd(23)} ${e.nextRotationDue || '—'}`);
5149
+ if (entries.length === 0) {
5150
+ println('No provider credentials stored in the rotation store.');
5151
+ } else {
5152
+ println('Provider credentials\n');
5153
+ println(' PROVIDER ACCOUNT ROTATED_AT NEXT_ROTATION');
5154
+ for (const e of entries) {
5155
+ println(` ${e.provider.padEnd(26)} ${(e.account || '—').padEnd(27)} ${(e.rotatedAt || '—').padEnd(23)} ${e.nextRotationDue || '—'}`);
5156
+ }
4890
5157
  }
5158
+ try {
5159
+ const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
5160
+ const { providers } = getProviderModelCatalog({ env: process.env });
5161
+ println('\nLLM provider readiness (op:// references resolve at call time)\n');
5162
+ for (const p of providers) {
5163
+ println(` ${p.configured ? '✓' : '·'} ${p.id}`);
5164
+ }
5165
+ println('\n Set keys as values or op:// refs in ~/.construct/config.env; run `construct creds login copilot` for GitHub Copilot.');
5166
+ } catch { /* readiness view is informational */ }
4891
5167
  return;
4892
5168
  }
4893
5169
 
@@ -4926,7 +5202,68 @@ async function cmdCreds(args) {
4926
5202
  return;
4927
5203
  }
4928
5204
 
4929
- errorln(`Unknown creds subcommand: ${sub}. Available: list, set, rotate, revoke, test`);
5205
+ if (sub === 'login') {
5206
+ const provider = args[1] || 'copilot';
5207
+ if (provider !== 'copilot' && provider !== 'github-copilot') {
5208
+ errorln(`\`creds login\` currently supports only \`copilot\`. For API-key providers use \`construct creds set ${provider}\`.`);
5209
+ process.exit(1);
5210
+ }
5211
+ const copilot = await import('../lib/providers/copilot-auth.mjs');
5212
+ if (copilot.hasStoredCredential() && !args.includes('--force')) {
5213
+ try {
5214
+ await copilot.getCopilotToken();
5215
+ println('GitHub Copilot is already authenticated and the session token exchanged successfully.');
5216
+ println('Re-run with --force to authenticate a different account.');
5217
+ return;
5218
+ } catch {
5219
+ println('A stored Copilot credential exists but the session exchange failed; re-authenticating.');
5220
+ }
5221
+ }
5222
+ let device;
5223
+ try {
5224
+ device = await copilot.requestDeviceCode({});
5225
+ } catch (err) {
5226
+ errorln(err.message);
5227
+ process.exit(1);
5228
+ }
5229
+ println('\nGitHub Copilot sign-in');
5230
+ println(` 1. Open: ${device.verificationUri}`);
5231
+ println(` 2. Enter code: ${device.userCode}`);
5232
+ println('\nWaiting for authorization...');
5233
+ let token;
5234
+ try {
5235
+ token = await copilot.pollForAccessToken({
5236
+ deviceCode: device.deviceCode,
5237
+ interval: device.interval,
5238
+ expiresIn: device.expiresIn,
5239
+ onPending: () => process.stdout.write('.'),
5240
+ });
5241
+ process.stdout.write('\n');
5242
+ } catch (err) {
5243
+ process.stdout.write('\n');
5244
+ errorln(err.message);
5245
+ process.exit(1);
5246
+ }
5247
+ copilot.persistOAuth({ accessToken: token.accessToken, refreshToken: token.refreshToken, expiresAt: token.expiresAt, user: null });
5248
+ try {
5249
+ await copilot.getCopilotToken();
5250
+ println('GitHub Copilot authenticated. The session token exchanged successfully.');
5251
+ println('Credentials stored in ~/.construct/auth/github-copilot.json and ~/.config/github-copilot/apps.json.');
5252
+ try {
5253
+ const models = await copilot.listCopilotModels({});
5254
+ if (models.length) {
5255
+ println('\nModels available to your account (use as github-copilot/<id>):');
5256
+ for (const id of models) println(` - github-copilot/${id}`);
5257
+ }
5258
+ } catch { /* model listing is informational; auth already succeeded */ }
5259
+ } catch (err) {
5260
+ errorln(`Authenticated with GitHub, but the Copilot token exchange failed: ${err.message}`);
5261
+ process.exit(1);
5262
+ }
5263
+ return;
5264
+ }
5265
+
5266
+ errorln(`Unknown creds subcommand: ${sub}. Available: list, login, set, rotate, revoke, test`);
4930
5267
  process.exit(1);
4931
5268
  }
4932
5269
 
@@ -5149,7 +5486,7 @@ async function cmdTelemetryQuery(args) {
5149
5486
  println(' errors [--agent=<name>] [--since=24h] error span summary');
5150
5487
  println(' trace <traceId> full span tree');
5151
5488
  println('');
5152
- println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.mdx');
5489
+ println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.md');
5153
5490
  }
5154
5491
 
5155
5492
  async function cmdHook(args) {
@@ -5215,6 +5552,7 @@ const handlers = new Map([
5215
5552
  ['status', cmdStatus],
5216
5553
  ['install', cmdInstall],
5217
5554
  ['config', cmdConfig],
5555
+ ['sources', cmdSources],
5218
5556
  ['intake', cmdIntake],
5219
5557
  ['recommendations', cmdRecommendations],
5220
5558
  ['integrations', cmdIntegrations],
@@ -5233,13 +5571,18 @@ const handlers = new Map([
5233
5571
  ['distill', cmdDistill],
5234
5572
  ['ingest', cmdIngest],
5235
5573
  ['export', cmdExport],
5574
+ ['publish', cmdPublish],
5575
+ ['tools', cmdTools],
5236
5576
  ['infer', cmdInfer],
5237
5577
  ['search', cmdSearch],
5238
5578
  ['storage', cmdStorage],
5239
5579
  ['registry:status', cmdRegistryStatus],
5580
+ ['registry:validate', cmdRegistryValidate],
5581
+ ['registry:generate-docs', cmdRegistryGenerateDocs],
5582
+ ['rules', cmdRulesUsage],
5240
5583
  // Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
5241
5584
  // catalog, and per-turn accounting still run, but no CLI surface exposes
5242
- // them to the user. Handlers (cmdPricing, cmdCosts, cmdCost) remain in this
5585
+ // them to the user. Handlers (_cmdPricing, _cmdCosts, _cmdCost) remain in this
5243
5586
  // file so the OTel + dashboard wiring (planned in Workstream J) can rewire
5244
5587
  // the surface without re-implementing the logic.
5245
5588
  ['overrides', cmdOverrides],
@@ -5262,7 +5605,7 @@ const handlers = new Map([
5262
5605
  if (sub === 'verify') return cmdDocsVerify(rest);
5263
5606
  if (sub === 'update') return cmdDocsUpdate(rest);
5264
5607
  if (sub === 'check') return cmdDocsCheck(rest);
5265
- if (sub === 'site') return cmdDocsSite();
5608
+ if (sub === 'site') return cmdDocsSite(rest);
5266
5609
  if (sub === 'reconcile') return cmdDocsReconcile(rest);
5267
5610
  errorln(`Unknown docs subcommand: ${sub}. Available: check, verify, update, site, reconcile`);
5268
5611
  process.exit(1);
@@ -5277,6 +5620,10 @@ const handlers = new Map([
5277
5620
  const { runAcpServer } = await import('../lib/acp/server.mjs');
5278
5621
  runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
5279
5622
  }],
5623
+ ['chat', async (args) => {
5624
+ const { runChat } = await import('../lib/chat/cli.mjs');
5625
+ process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
5626
+ }],
5280
5627
  ['beads:stats', async (args) => {
5281
5628
  const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
5282
5629
  const jsonOutput = args.includes('--json');
@@ -5438,11 +5785,40 @@ const handlers = new Map([
5438
5785
  ['audit', async (args) => {
5439
5786
  const sub = args[0];
5440
5787
  if (sub === 'skills') return runAuditSkillsCli(args.slice(1));
5788
+ if (sub === 'specialists') return runAuditSpecialistsCli(args.slice(1));
5789
+ if (sub === 'tests') {
5790
+ const rest = args.slice(1);
5791
+ if (rest.includes('--corpus')) return runCorpusInventoryAuditCli(rest.filter((arg) => arg !== '--corpus'), { rootDir: ROOT_DIR });
5792
+ return runCapabilityLedgerAuditCli(rest, { rootDir: ROOT_DIR });
5793
+ }
5441
5794
  if (sub === 'trail' || !sub) {
5442
5795
  const { runAuditTrailCli } = await import('../lib/audit-trail.mjs');
5443
5796
  return runAuditTrailCli(args.slice(sub === 'trail' ? 1 : 0));
5444
5797
  }
5445
- errorln(`Unknown audit subcommand: ${sub}. Available: skills, trail`);
5798
+ errorln(`Unknown audit subcommand: ${sub}. Available: skills, specialists, tests, trail`);
5799
+ process.exit(1);
5800
+ }],
5801
+ ['artifact', async (args) => {
5802
+ const sub = args[0];
5803
+ if (sub === 'validate') return runArtifactValidateCli(args.slice(1));
5804
+ if (sub === 'workflow') {
5805
+ const rest = args.slice(1);
5806
+ const request = rest.filter((arg) => !arg.startsWith('--')).join(' ');
5807
+ const read = (name) => rest.find((arg) => arg.startsWith(`${name}=`))?.slice(name.length + 1);
5808
+ const { runArtifactWorkflow } = await import('../lib/artifact-workflow.mjs');
5809
+ const result = runArtifactWorkflow({
5810
+ input: request,
5811
+ artifactType: read('--type'),
5812
+ filePath: read('--file'),
5813
+ format: read('--to'),
5814
+ outputPath: read('--output'),
5815
+ branding: rest.includes('--plain') ? 'plain' : undefined,
5816
+ approvalMode: rest.includes('--apply') ? 'allow-durable-write' : (read('--approval-mode') || 'proposal-only'),
5817
+ }, { cwd: process.cwd(), rootDir: ROOT_DIR });
5818
+ println(JSON.stringify(result, null, 2));
5819
+ return;
5820
+ }
5821
+ errorln(`Unknown artifact subcommand: ${sub || '(none)'}. Available: validate, workflow`);
5446
5822
  process.exit(1);
5447
5823
  }],
5448
5824
  ['doc', async (args) => {
@@ -5890,6 +6266,30 @@ const handlers = new Map([
5890
6266
  const { runEmbedCli } = await import('../lib/embed/cli.mjs');
5891
6267
  return runEmbedCli(args, { rootDir: new URL('..', import.meta.url).pathname });
5892
6268
  }],
6269
+ ['oracle', async (args) => {
6270
+ const { runOracleCli } = await import('../lib/oracle/cli.mjs');
6271
+ return runOracleCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd(), homeDir: HOME });
6272
+ }],
6273
+ ['certify', async (args) => {
6274
+ const { runCertificationCli } = await import('../lib/certification/cli.mjs');
6275
+ const code = await runCertificationCli(args, { projectDir: process.cwd(), repoRoot: ROOT_DIR });
6276
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6277
+ }],
6278
+ ['improvement', async (args) => {
6279
+ const { runImprovementCli } = await import('../lib/improvement/cli.mjs');
6280
+ const code = await runImprovementCli(args, { projectDir: process.cwd() });
6281
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6282
+ }],
6283
+ ['matrix', async (args) => {
6284
+ const { runGraphCli } = await import('../lib/graph/cli.mjs');
6285
+ const code = runGraphCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd() });
6286
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6287
+ }],
6288
+ ['impact', async (args) => {
6289
+ const { runImpactCli } = await import('../lib/graph/impact-cli.mjs');
6290
+ const code = await runImpactCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd() });
6291
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6292
+ }],
5893
6293
  ['reflect', async (args) => {
5894
6294
  const { runReflectCli } = await import('../lib/reflect.mjs');
5895
6295
  return runReflectCli(args);