@geraldmaron/construct 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (425) hide show
  1. package/README.md +30 -18
  2. package/bin/construct +392 -91
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +112 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +77 -10
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +232 -11
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +703 -0
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +302 -0
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +4 -2
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/doctor/watchers/service-health.mjs +1 -1
  124. package/lib/document-export.mjs +360 -36
  125. package/lib/document-extract.mjs +88 -0
  126. package/lib/document-ingest.mjs +32 -18
  127. package/lib/embed/cli.mjs +2 -2
  128. package/lib/embed/daemon.mjs +18 -46
  129. package/lib/embed/semantic.mjs +5 -2
  130. package/lib/embed/worker.mjs +2 -2
  131. package/lib/embedded-contract/model-resolve.mjs +10 -9
  132. package/lib/env-config.mjs +26 -0
  133. package/lib/evals/dataset.mjs +137 -0
  134. package/lib/evals/gates.mjs +175 -0
  135. package/lib/graph/build-co-change.mjs +54 -0
  136. package/lib/graph/build-from-registry.mjs +136 -0
  137. package/lib/graph/build-import-graph.mjs +153 -0
  138. package/lib/graph/cli.mjs +110 -0
  139. package/lib/graph/impact-cli.mjs +89 -0
  140. package/lib/graph/impact.mjs +108 -0
  141. package/lib/graph/staleness.mjs +44 -0
  142. package/lib/graph/store.mjs +172 -0
  143. package/lib/handoffs/cleanup.mjs +1 -1
  144. package/lib/health-check.mjs +90 -76
  145. package/lib/hooks/artifact-release-gate.mjs +43 -0
  146. package/lib/hooks/audit-trail.mjs +14 -28
  147. package/lib/hooks/brand-prose-lint.mjs +38 -0
  148. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  149. package/lib/hooks/session-optimize.mjs +84 -207
  150. package/lib/hooks/session-start.mjs +4 -5
  151. package/lib/host-disposition.mjs +7 -0
  152. package/lib/improvement/cli.mjs +189 -0
  153. package/lib/improvement/controller.mjs +137 -0
  154. package/lib/improvement/proposal.mjs +120 -0
  155. package/lib/improvement/specialist-loop.mjs +192 -0
  156. package/lib/improvement/store.mjs +89 -0
  157. package/lib/improvement/surface.mjs +219 -0
  158. package/lib/ingest-tooling.mjs +97 -0
  159. package/lib/init/doc-lanes.mjs +165 -0
  160. package/lib/init-docs.mjs +31 -178
  161. package/lib/init-unified.mjs +27 -97
  162. package/lib/init-update-guide.mjs +102 -0
  163. package/lib/init-update.mjs +32 -1
  164. package/lib/init.mjs +8 -0
  165. package/lib/install/desktop-binary-download.mjs +85 -0
  166. package/lib/install/legacy-global-cleanup.mjs +189 -0
  167. package/lib/intake/daemon.mjs +2 -0
  168. package/lib/intake/git-queue.mjs +9 -8
  169. package/lib/intake/queue.mjs +2 -1
  170. package/lib/intake/session-prelude.mjs +90 -1
  171. package/lib/libreoffice-export.mjs +97 -0
  172. package/lib/logging/rotate.mjs +9 -0
  173. package/lib/maintenance/docker-reclaim.mjs +206 -0
  174. package/lib/mcp/external-schema-cost.mjs +74 -0
  175. package/lib/mcp/server.mjs +84 -9
  176. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  177. package/lib/mcp/tool-budget.mjs +55 -4
  178. package/lib/mcp/tools/document.mjs +18 -4
  179. package/lib/mcp/tools/skills.mjs +32 -3
  180. package/lib/mcp/tools/workflow.mjs +13 -1
  181. package/lib/model-free-selector.mjs +18 -0
  182. package/lib/model-registry.mjs +13 -229
  183. package/lib/model-router.mjs +425 -93
  184. package/lib/models/behavior-matrix.mjs +289 -0
  185. package/lib/models/catalog.mjs +209 -0
  186. package/lib/models/execution-capability-profile.mjs +196 -0
  187. package/lib/models/execution-policy.mjs +307 -0
  188. package/lib/models/provider-poll.mjs +383 -0
  189. package/lib/npm-spawn-env.mjs +17 -0
  190. package/lib/ollama/installed-models.mjs +129 -0
  191. package/lib/oracle/actions.mjs +187 -0
  192. package/lib/oracle/artifact-gate.mjs +99 -0
  193. package/lib/oracle/cli.mjs +204 -0
  194. package/lib/oracle/daemon-entry.mjs +14 -0
  195. package/lib/oracle/dispatch.mjs +81 -0
  196. package/lib/oracle/execute.mjs +143 -0
  197. package/lib/oracle/gaps.mjs +76 -0
  198. package/lib/oracle/index.mjs +84 -0
  199. package/lib/oracle/issues.mjs +164 -0
  200. package/lib/oracle/org-graph.mjs +170 -0
  201. package/lib/oracle/policy.mjs +80 -0
  202. package/lib/oracle/read-model.mjs +398 -0
  203. package/lib/oracle/reconcile.mjs +191 -0
  204. package/lib/oracle/routing.mjs +89 -0
  205. package/lib/oracle/synthesize.mjs +384 -0
  206. package/lib/oracle/verdicts.mjs +51 -0
  207. package/lib/orchestration/worker.mjs +88 -27
  208. package/lib/orchestration-policy.mjs +50 -0
  209. package/lib/parity.mjs +96 -2
  210. package/lib/persona-sections.mjs +66 -0
  211. package/lib/playwright-demo.mjs +252 -0
  212. package/lib/project-init-shared.mjs +4 -1
  213. package/lib/prompt-composer.js +9 -4
  214. package/lib/prompt-validation-contract.mjs +24 -0
  215. package/lib/provider-capabilities.js +57 -11
  216. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  217. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  218. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  219. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  220. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  221. package/lib/providers/contract/contract-tests.mjs +57 -0
  222. package/lib/providers/contract/errors.mjs +48 -0
  223. package/lib/providers/contract/interface.mjs +50 -0
  224. package/lib/providers/contract/registry.mjs +102 -0
  225. package/lib/providers/copilot-auth.mjs +297 -0
  226. package/lib/providers/credential-bootstrap.mjs +178 -0
  227. package/lib/providers/credential-catalog.mjs +46 -0
  228. package/lib/providers/credential-sources.mjs +63 -0
  229. package/lib/providers/creds.mjs +5 -2
  230. package/lib/providers/op-run.mjs +59 -0
  231. package/lib/providers/secret-resolver.mjs +159 -0
  232. package/lib/publish-template.mjs +163 -0
  233. package/lib/publish-tooling.mjs +119 -0
  234. package/lib/publish.mjs +305 -0
  235. package/lib/registry/cli.mjs +82 -0
  236. package/lib/registry/consolidation.mjs +147 -0
  237. package/lib/registry/generate-docs.mjs +75 -0
  238. package/lib/registry/skill-verification.mjs +57 -0
  239. package/lib/registry/surface-map.mjs +76 -0
  240. package/lib/registry/validate.mjs +135 -0
  241. package/lib/resources/budget.mjs +82 -0
  242. package/lib/resources/process-budget.mjs +45 -0
  243. package/lib/rules-delivery.mjs +9 -2
  244. package/lib/rules-read.mjs +26 -0
  245. package/lib/runtime-env.mjs +23 -0
  246. package/lib/runtime-pressure.mjs +51 -7
  247. package/lib/schema-infer.mjs +13 -2
  248. package/lib/server/chat-loop.mjs +622 -0
  249. package/lib/server/demo-preview.mjs +63 -0
  250. package/lib/server/index.mjs +229 -8
  251. package/lib/server/langfuse-login.mjs +3 -3
  252. package/lib/service-manager.mjs +61 -9
  253. package/lib/session-store.mjs +1 -1
  254. package/lib/setup.mjs +102 -2
  255. package/lib/specialists/prompt-schema.mjs +19 -10
  256. package/lib/specialists/roster.mjs +43 -0
  257. package/lib/specialists/scaffold.mjs +56 -0
  258. package/lib/storage/backend.mjs +6 -6
  259. package/lib/storage/embeddings-local.mjs +6 -3
  260. package/lib/storage/file-lock.mjs +18 -11
  261. package/lib/storage/hybrid-query.mjs +7 -4
  262. package/lib/storage/state-source.mjs +5 -5
  263. package/lib/storage/sync.mjs +2 -3
  264. package/lib/telemetry/rule-calls.mjs +52 -0
  265. package/lib/template-registry.mjs +2 -2
  266. package/lib/templates/visual-requirements.mjs +27 -51
  267. package/lib/test-corpus-inventory.mjs +313 -0
  268. package/lib/uninstall/uninstall.mjs +19 -7
  269. package/lib/update.mjs +12 -0
  270. package/lib/upgrade.mjs +14 -0
  271. package/lib/wireframe.mjs +20 -14
  272. package/lib/worker/run.mjs +17 -4
  273. package/lib/worker/trace.mjs +11 -3
  274. package/package.json +29 -13
  275. package/personas/construct.md +13 -13
  276. package/platforms/claude/settings.template.json +36 -0
  277. package/rules/common/patterns.md +1 -1
  278. package/rules/common/release-gates.md +4 -3
  279. package/scripts/sync-specialists.mjs +187 -60
  280. package/skills/devops/data-engineering.md +1 -1
  281. package/skills/docs/adr-workflow.md +1 -0
  282. package/skills/docs/backlog-proposal-workflow.md +1 -0
  283. package/skills/docs/codebase-research-workflow.md +40 -0
  284. package/skills/docs/customer-profile-workflow.md +1 -0
  285. package/skills/docs/document-ingest-workflow.md +1 -0
  286. package/skills/docs/evidence-ingest-workflow.md +1 -0
  287. package/skills/docs/init-docs.md +2 -2
  288. package/skills/docs/init-project.md +8 -3
  289. package/skills/docs/prd-workflow.md +24 -1
  290. package/skills/docs/prfaq-workflow.md +1 -0
  291. package/skills/docs/product-intelligence-workflow.md +1 -0
  292. package/skills/docs/product-signal-workflow.md +1 -0
  293. package/skills/docs/research-workflow.md +54 -37
  294. package/skills/docs/runbook-workflow.md +1 -0
  295. package/skills/docs/strategy-workflow.md +1 -0
  296. package/skills/docs/user-research-workflow.md +40 -0
  297. package/skills/operating/orchestration-reference.md +1 -1
  298. package/skills/roles/architect.md +5 -0
  299. package/skills/roles/operator.docs.md +4 -0
  300. package/skills/routing.md +4 -2
  301. package/specialists/artifact-manifest.json +480 -0
  302. package/specialists/artifact-manifest.schema.json +53 -0
  303. package/specialists/audit-enrichments.json +454 -0
  304. package/specialists/contracts.json +37 -25
  305. package/specialists/prompts/_shared/validation-contract.md +26 -0
  306. package/specialists/prompts/cx-accessibility.md +21 -2
  307. package/specialists/prompts/cx-ai-engineer.md +24 -1
  308. package/specialists/prompts/cx-architect.md +4 -1
  309. package/specialists/prompts/cx-business-strategist.md +23 -2
  310. package/specialists/prompts/cx-data-analyst.md +22 -1
  311. package/specialists/prompts/cx-data-engineer.md +23 -2
  312. package/specialists/prompts/cx-debugger.md +21 -2
  313. package/specialists/prompts/cx-designer.md +26 -3
  314. package/specialists/prompts/cx-devil-advocate.md +19 -2
  315. package/specialists/prompts/cx-docs-keeper.md +28 -1
  316. package/specialists/prompts/cx-engineer.md +22 -1
  317. package/specialists/prompts/cx-evaluator.md +18 -1
  318. package/specialists/prompts/cx-explorer.md +21 -2
  319. package/specialists/prompts/cx-legal-compliance.md +21 -2
  320. package/specialists/prompts/cx-operations.md +21 -2
  321. package/specialists/prompts/cx-oracle.md +94 -0
  322. package/specialists/prompts/cx-orchestrator.md +20 -1
  323. package/specialists/prompts/cx-platform-engineer.md +25 -2
  324. package/specialists/prompts/cx-product-manager.md +32 -1
  325. package/specialists/prompts/cx-qa.md +24 -2
  326. package/specialists/prompts/cx-rd-lead.md +23 -2
  327. package/specialists/prompts/cx-release-manager.md +23 -2
  328. package/specialists/prompts/cx-researcher.md +22 -2
  329. package/specialists/prompts/cx-reviewer.md +18 -1
  330. package/specialists/prompts/cx-security.md +22 -2
  331. package/specialists/prompts/cx-sre.md +30 -3
  332. package/specialists/prompts/cx-test-automation.md +7 -1
  333. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  334. package/specialists/prompts/cx-ux-researcher.md +21 -2
  335. package/specialists/registry.json +52 -173
  336. package/specialists/tone-profiles.json +42 -0
  337. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  338. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  339. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  340. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  341. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  342. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  343. package/templates/demos/vhs/construct-cockpit.json +24 -0
  344. package/templates/distribution/construct-brand.typ +446 -0
  345. package/templates/distribution/construct-decision.typ +38 -0
  346. package/templates/distribution/construct-deck.html +95 -0
  347. package/templates/distribution/construct-pdf.typ +38 -0
  348. package/templates/distribution/construct-prd.typ +38 -0
  349. package/templates/distribution/construct-research.typ +38 -0
  350. package/templates/distribution/construct-web.html +92 -0
  351. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  352. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  353. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  354. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  355. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  356. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  357. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  358. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  359. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  360. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  361. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  362. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  363. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  364. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  365. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  366. package/templates/distribution/fonts/README.md +36 -0
  367. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  368. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  369. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  370. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  371. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  372. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  373. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  374. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  375. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  376. package/templates/distribution/icons/activity.svg +15 -0
  377. package/templates/distribution/icons/alert-triangle.svg +17 -0
  378. package/templates/distribution/icons/book-open.svg +16 -0
  379. package/templates/distribution/icons/bot.svg +20 -0
  380. package/templates/distribution/icons/brain.svg +22 -0
  381. package/templates/distribution/icons/circle-check.svg +16 -0
  382. package/templates/distribution/icons/clipboard-check.svg +17 -0
  383. package/templates/distribution/icons/cpu.svg +28 -0
  384. package/templates/distribution/icons/database.svg +17 -0
  385. package/templates/distribution/icons/eye.svg +16 -0
  386. package/templates/distribution/icons/file-text.svg +19 -0
  387. package/templates/distribution/icons/gauge.svg +16 -0
  388. package/templates/distribution/icons/git-branch.svg +17 -0
  389. package/templates/distribution/icons/key.svg +17 -0
  390. package/templates/distribution/icons/layers.svg +17 -0
  391. package/templates/distribution/icons/list-checks.svg +19 -0
  392. package/templates/distribution/icons/lock.svg +16 -0
  393. package/templates/distribution/icons/message-square.svg +15 -0
  394. package/templates/distribution/icons/network.svg +19 -0
  395. package/templates/distribution/icons/route.svg +17 -0
  396. package/templates/distribution/icons/scale.svg +19 -0
  397. package/templates/distribution/icons/search.svg +16 -0
  398. package/templates/distribution/icons/send.svg +16 -0
  399. package/templates/distribution/icons/server.svg +18 -0
  400. package/templates/distribution/icons/shield-check.svg +16 -0
  401. package/templates/distribution/icons/users.svg +18 -0
  402. package/templates/distribution/icons/webhook.svg +17 -0
  403. package/templates/distribution/icons/workflow.svg +17 -0
  404. package/templates/distribution/icons/wrench.svg +15 -0
  405. package/templates/distribution/run.mjs +18 -1
  406. package/templates/docs/adr.md +7 -0
  407. package/templates/docs/construct_guide.md +10 -10
  408. package/templates/docs/customer-profile.md +4 -0
  409. package/templates/docs/one-pager.md +4 -0
  410. package/templates/docs/postmortem.md +31 -0
  411. package/templates/docs/prd-platform.md +19 -0
  412. package/templates/docs/prd.md +15 -0
  413. package/templates/docs/prfaq.md +7 -0
  414. package/templates/docs/qa-strategy.md +103 -0
  415. package/templates/docs/research-brief.md +8 -0
  416. package/templates/docs/rfc-platform.md +12 -0
  417. package/templates/docs/test-plan.md +7 -0
  418. package/lib/ingest/chunker.mjs +0 -94
  419. package/lib/ingest/pipeline.mjs +0 -53
  420. package/lib/ingest/store.mjs +0 -82
  421. package/lib/mode-commands.mjs +0 -122
  422. package/lib/policy/unified-gates.mjs +0 -96
  423. package/lib/profiles/validate-custom.mjs +0 -114
  424. package/lib/services/telemetry-backend.mjs +0 -177
  425. package/lib/storage/fusion.mjs +0 -95
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 });
@@ -677,6 +673,14 @@ async function cmdDoctor() {
677
673
 
678
674
  add('Node.js 20+ (recommended)', Number.parseInt(process.versions.node.split('.')[0], 10) >= 20);
679
675
  add('npm available', true);
676
+ const npmDevdir = process.env.npm_config_devdir ?? process.env.NPM_CONFIG_DEVDIR;
677
+ if (npmDevdir) {
678
+ add(
679
+ '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 ...',
680
+ false,
681
+ true,
682
+ );
683
+ }
680
684
  const constructOnPath = spawnSync('zsh', ['-lc', 'command -v construct'], { encoding: 'utf8', env: process.env });
681
685
  const constructPath = constructOnPath.status === 0 ? constructOnPath.stdout.trim() : '';
682
686
  const constructVersion = constructPath
@@ -854,6 +858,8 @@ async function cmdDoctor() {
854
858
  add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
855
859
  const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
856
860
  add('No declared skills missing on disk', skillAudit.pass, false);
861
+ const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
862
+ add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
857
863
  const { lintResearchRepo } = await import('../lib/research-lint.mjs');
858
864
  const researchLintResults = lintResearchRepo({ rootDir: process.cwd() });
859
865
  add('Research artifacts meet minimum evidence structure', researchLintResults.every((entry) => entry.errors.length === 0), true);
@@ -916,6 +922,14 @@ async function cmdDoctor() {
916
922
  add('Cross-surface adapter parity', false);
917
923
  }
918
924
 
925
+ try {
926
+ const { checkProjectAdaptersForDoctor } = await import('../lib/doctor/project-adapters.mjs');
927
+ const projectAdapters = checkProjectAdaptersForDoctor({ rootDir: ROOT_DIR, projectDir: process.cwd() });
928
+ add(projectAdapters.label, projectAdapters.ok, projectAdapters.warning);
929
+ } catch {
930
+ add('Project adapters', false, true);
931
+ }
932
+
919
933
  try {
920
934
  const { validateSkills } = await import('../lib/validators/skills.mjs');
921
935
  const skillsRoot = path.join(ROOT_DIR, 'skills');
@@ -928,6 +942,28 @@ async function cmdDoctor() {
928
942
  add('Skill structure', false, true);
929
943
  }
930
944
 
945
+ try {
946
+ const { lintWorkflowSkillVerification } = await import('../lib/registry/skill-verification.mjs');
947
+ const wf = lintWorkflowSkillVerification(path.join(ROOT_DIR, 'skills'));
948
+ const wfLabel = wf.warnings.length
949
+ ? `Workflow skill verification (${wf.checked} checked, ${wf.warnings.length} missing bars)`
950
+ : `Workflow skill verification (${wf.checked} checked)`;
951
+ add(wfLabel, wf.valid && wf.warnings.length === 0, true);
952
+ } catch {
953
+ add('Workflow skill verification', false, true);
954
+ }
955
+
956
+ try {
957
+ const { validateCapabilityRegistry } = await import('../lib/registry/validate.mjs');
958
+ const reg = validateCapabilityRegistry({ rootDir: ROOT_DIR });
959
+ const regLabel = reg.warnings.length
960
+ ? `Capability registry (${reg.count} entries, ${reg.warnings.length} warnings)`
961
+ : `Capability registry (${reg.count} entries)`;
962
+ add(regLabel, reg.valid, reg.errors.length > 0);
963
+ } catch {
964
+ add('Capability registry', false, true);
965
+ }
966
+
931
967
  try {
932
968
  const { describeBreakers, STATES } = await import('../lib/providers/circuit-breaker.mjs');
933
969
  const breakers = describeBreakers();
@@ -1036,7 +1072,7 @@ async function cmdDoctor() {
1036
1072
  }
1037
1073
 
1038
1074
  // Agent contracts integrity
1039
- const { getAllContracts, summarize } = await import('../lib/specialist-contracts.mjs');
1075
+ const { getAllContracts } = await import('../lib/specialist-contracts.mjs');
1040
1076
  const contracts = getAllContracts();
1041
1077
  add('Agent contracts loaded', contracts.length > 0);
1042
1078
  if (contracts.length > 0) {
@@ -1229,7 +1265,7 @@ async function resolveServiceSelection(args, selectable) {
1229
1265
  return null;
1230
1266
  }
1231
1267
 
1232
- async function cmdUp(args = []) {
1268
+ async function cmdDev(args = []) {
1233
1269
  const { SELECTABLE_SERVICES } = await import('../lib/service-manager.mjs');
1234
1270
 
1235
1271
  // --only=a,b,c picks services non-interactively; --select opens a checklist.
@@ -1242,7 +1278,7 @@ async function cmdUp(args = []) {
1242
1278
  opLog.event('selection', { services: selected ? [...selected] : 'all' });
1243
1279
 
1244
1280
  // Auto-magic health check — catch missing prerequisites before starting services
1245
- const { quickHealthCheck, detectCredentials, resolveCredentials } = await import('../lib/health-check.mjs');
1281
+ const { quickHealthCheck, resolveCredentials } = await import('../lib/health-check.mjs');
1246
1282
 
1247
1283
  // Resolve credentials from env, .env files, and 1Password before starting services
1248
1284
  const resolvedCreds = await resolveCredentials({ homeDir: HOME });
@@ -1365,7 +1401,7 @@ async function cmdUp(args = []) {
1365
1401
  opLog.close(failed.length ? 'degraded' : 'ok', { failed: failed.map((r) => r.name) });
1366
1402
  }
1367
1403
 
1368
- async function cmdDown() {
1404
+ async function cmdStop() {
1369
1405
  const { stopServices } = await import('../lib/service-manager.mjs');
1370
1406
  const result = await stopServices({ homeDir: HOME, rootDir: ROOT_DIR });
1371
1407
  for (const svc of result.results) {
@@ -1384,7 +1420,7 @@ async function cmdDown() {
1384
1420
  } catch { /* best effort */ }
1385
1421
  }
1386
1422
 
1387
- async function cmdServe() {
1423
+ async function cmdDashboard() {
1388
1424
  if (restArgsCache.includes('--token')) {
1389
1425
  const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
1390
1426
  const existing = getDashboardToken();
@@ -1524,6 +1560,7 @@ async function cmdExport(args) {
1524
1560
  const toFlag = args.find((a) => a.startsWith('--to='))?.split('=')[1];
1525
1561
  const outFlag = args.find((a) => a.startsWith('--output='))?.split('=')[1];
1526
1562
  const detectOnly = args.includes('--detect');
1563
+ const figures = !args.includes('--no-figures');
1527
1564
 
1528
1565
  const { detect, exportMarkdown, EXPORT_FORMATS } = await import('../lib/document-export.mjs');
1529
1566
 
@@ -1534,7 +1571,7 @@ async function cmdExport(args) {
1534
1571
  }
1535
1572
 
1536
1573
  if (!inputPath) {
1537
- errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--detect]');
1574
+ errorln('Usage: construct export <markdown-file> --to=<pdf|docx|html> [--output=<path>] [--figures|--no-figures] [--detect]');
1538
1575
  process.exit(1);
1539
1576
  }
1540
1577
  if (!toFlag) {
@@ -1544,7 +1581,13 @@ async function cmdExport(args) {
1544
1581
 
1545
1582
  const resolvedIn = path.resolve(process.cwd(), inputPath);
1546
1583
  const resolvedOut = outFlag ? path.resolve(process.cwd(), outFlag) : null;
1547
- const result = exportMarkdown({ inputPath: resolvedIn, outputPath: resolvedOut, format: toFlag });
1584
+ const result = exportMarkdown({
1585
+ inputPath: resolvedIn,
1586
+ outputPath: resolvedOut,
1587
+ format: toFlag,
1588
+ figures: toFlag === 'pdf' ? figures : false,
1589
+ repoRoot: ROOT_DIR,
1590
+ });
1548
1591
  if (!result.ok) {
1549
1592
  errorln(result.message);
1550
1593
  process.exit(result.missing?.length ? 2 : 1);
@@ -1552,6 +1595,36 @@ async function cmdExport(args) {
1552
1595
  ok(result.message);
1553
1596
  }
1554
1597
 
1598
+ async function cmdPublish(args) {
1599
+ const { runPublishCli } = await import('../lib/publish.mjs');
1600
+ const { exitCode } = await runPublishCli(args, { cwd: process.cwd(), repoRoot: ROOT_DIR });
1601
+ process.exit(exitCode ?? 0);
1602
+ }
1603
+
1604
+ async function cmdTools(args) {
1605
+ const [sub, ...rest] = args;
1606
+ if (sub !== 'detect') {
1607
+ errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME] [--dashboard-demo=NAME]');
1608
+ process.exit(1);
1609
+ }
1610
+ const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
1611
+ const json = rest.includes('--json');
1612
+ const figures = !rest.includes('--no-figures');
1613
+ const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
1614
+ const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
1615
+ const detection = detectPublishPipeline({
1616
+ format: 'pdf',
1617
+ includeFigures: figures,
1618
+ includeTerminalDemo: demos.length > 0,
1619
+ includeDashboardDemo: dashboardDemos.length > 0,
1620
+ cwd: process.cwd(),
1621
+ repoRoot: ROOT_DIR,
1622
+ });
1623
+ if (json) println(formatToolsDetectReport(detection, { json: true }));
1624
+ else println(formatToolsDetectReport(detection));
1625
+ process.exit(detection.present ? 0 : 2);
1626
+ }
1627
+
1555
1628
  async function cmdInfer(args) {
1556
1629
  try {
1557
1630
  const result = await runInferCli(args, { cwd: process.cwd() });
@@ -1667,7 +1740,7 @@ async function cmdPrune(args) {
1667
1740
  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
1741
  }
1669
1742
 
1670
- async function cmdCosts(args) {
1743
+ async function _cmdCosts(args) {
1671
1744
  const sub = args[0] || 'status';
1672
1745
  const {
1673
1746
  getDailySpend,
@@ -1882,7 +1955,7 @@ async function cmdOverrides(args) {
1882
1955
  process.exit(1);
1883
1956
  }
1884
1957
 
1885
- async function cmdPricing(args) {
1958
+ async function _cmdPricing(args) {
1886
1959
  const sub = args[0] || 'status';
1887
1960
  const {
1888
1961
  describePricingCatalogFreshness,
@@ -1951,7 +2024,7 @@ async function cmdHosts() {
1951
2024
  printHostCapabilities();
1952
2025
  }
1953
2026
  }
1954
- async function cmdSetup(args) {
2027
+ async function cmdInstall(args) {
1955
2028
  const { startOpLog } = await import('../lib/op-log.mjs');
1956
2029
  const opLog = startOpLog('install', { homeDir: HOME });
1957
2030
  opLog.event('args', { args });
@@ -2080,7 +2153,7 @@ async function cmdEmbeddedTriage(args) {
2080
2153
  input = fs.readFileSync(0, 'utf8');
2081
2154
  }
2082
2155
  if (!input.trim() && !ingestion?.error) {
2083
- // This is an embedded-contract verb: an embedder that pipes empty/no input
2156
+ // Embedded-contract verb: an embedder that pipes empty/no input
2084
2157
  // must still get a parseable, versioned envelope — not a bare exit. Emit a
2085
2158
  // typed error envelope on stdout so the contract holds, and exit non-zero so
2086
2159
  // shell callers still see failure.
@@ -2402,7 +2475,7 @@ async function cmdIntakeProcess(args, cwd) {
2402
2475
  errorln('Choose one:');
2403
2476
  errorln(' construct intake process --wait Block until the holder finishes (default 30s).');
2404
2477
  errorln(' construct intake process --wait=<sec> Block for a custom window.');
2405
- errorln(' construct down Stop background services, then retry.');
2478
+ errorln(' construct stop Stop background services, then retry.');
2406
2479
  process.exit(2);
2407
2480
  }
2408
2481
  throw err;
@@ -3078,7 +3151,7 @@ async function cmdWorkspace(args) {
3078
3151
  const sub = args[0];
3079
3152
  if (!sub || sub === '--help' || sub === '-h') { printWorkspaceHelp(); return; }
3080
3153
 
3081
- const { createWorkspace, getWorkspace, listWorkspaces, updateWorkspace } = await import('../lib/embed/workspaces.mjs');
3154
+ const { createWorkspace, getWorkspace, listWorkspaces } = await import('../lib/embed/workspaces.mjs');
3082
3155
 
3083
3156
  if (sub === 'list') {
3084
3157
  const workspaces = listWorkspaces();
@@ -3192,7 +3265,6 @@ async function cmdRecommendations(args) {
3192
3265
  const reasonFlag = args.find(a => a.startsWith('--reason='))?.split('=').slice(1).join('=');
3193
3266
  const suppressDaysFlag = args.find(a => a.startsWith('--suppress-days='))?.split('=')[1];
3194
3267
  try {
3195
- const { dedupKey: computeKey } = await import('../lib/embed/recommendation-store.mjs');
3196
3268
  const key = `${recType}::${recTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-').slice(0, 60)}`;
3197
3269
  const { dismissRecommendation } = await import('../lib/embed/recommendation-store.mjs');
3198
3270
  dismissRecommendation(key, {
@@ -3602,6 +3674,7 @@ async function cmdModels(args) {
3602
3674
  return;
3603
3675
  }
3604
3676
  if (tier && setModel) {
3677
+ const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3605
3678
  const preferFree = args.includes('--prefer-free');
3606
3679
  const preferFreeSameFamily = args.includes('--prefer-free-same-family');
3607
3680
  const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
@@ -3647,7 +3720,7 @@ async function cmdModels(args) {
3647
3720
  return;
3648
3721
  }
3649
3722
  if (args.includes('--cheapest')) {
3650
- const { selectCheapestProvider, rankConfiguredProvidersByCost, formatCheapestProviderMessage, isCheapestProviderEnabled } =
3723
+ const { selectCheapestProvider, formatCheapestProviderMessage, isCheapestProviderEnabled } =
3651
3724
  await import('../lib/model-cheapest-provider.mjs');
3652
3725
  const cheapestTier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? 'standard';
3653
3726
  const verbose = args.includes('--verbose');
@@ -3659,7 +3732,7 @@ async function cmdModels(args) {
3659
3732
  return;
3660
3733
  }
3661
3734
  if (args.includes('--apply-cheapest')) {
3662
- const { selectCheapestForAllTiers, setCheapestProviderPreference } =
3735
+ const { setCheapestProviderPreference } =
3663
3736
  await import('../lib/model-cheapest-provider.mjs');
3664
3737
  const allTiers = args.includes('--all-tiers') || !args.find((arg) => arg.startsWith('--tier='));
3665
3738
  const tierArg = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1];
@@ -3684,6 +3757,22 @@ async function cmdModels(args) {
3684
3757
  await cmdSync([]);
3685
3758
  return;
3686
3759
  }
3760
+ if (args.includes('--list') || args[0] === 'list') {
3761
+ const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
3762
+ const { listChatModels } = await import('../apps/chat/engine/models.mjs');
3763
+ const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
3764
+ const models = listChatModels({ env: process.env, cwd: process.cwd() });
3765
+ if (args.includes('--json')) {
3766
+ println(JSON.stringify({ catalog, models }, null, 2));
3767
+ return;
3768
+ }
3769
+ println('Visible models (filtered by construct.config.json models.visibility):');
3770
+ for (const model of models) {
3771
+ const flag = model.available ? 'ok' : '—';
3772
+ println(` ${flag} ${model.id}${model.configured ? '' : ' (provider not configured)'}`);
3773
+ }
3774
+ return;
3775
+ }
3687
3776
  const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3688
3777
  const result = readCurrentModels(envPath, registry.models ?? {});
3689
3778
  const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
@@ -3937,10 +4026,21 @@ async function cmdCleanup() {
3937
4026
  println(` ! ${err.step}: ${err.error}`);
3938
4027
  }
3939
4028
  }
4029
+
4030
+ // Reclaim orphaned Postgres containers/volumes from deleted home namespaces.
4031
+ const { reclaimOrphanedDockerResources, formatReclaim } = await import('../lib/maintenance/docker-reclaim.mjs');
4032
+ const reclaim = reclaimOrphanedDockerResources({ homeDir: HOME, env: process.env, dryRun });
4033
+ if (!quiet) {
4034
+ const line = formatReclaim(reclaim);
4035
+ if (line) println(` ${line}`);
4036
+ for (const entry of reclaim.errors ?? []) {
4037
+ println(` ! docker reclaim ${entry.name}: ${entry.error}`);
4038
+ }
4039
+ }
3940
4040
  }
3941
4041
  }
3942
4042
 
3943
- async function cmdCost(args) {
4043
+ async function _cmdCost(args) {
3944
4044
  const jsonOutput = args.includes('--json');
3945
4045
  const resetFlag = args.includes('--reset');
3946
4046
  const daysArg = args.find((a) => a.startsWith('--days='));
@@ -4191,8 +4291,21 @@ async function cmdDocsCheck(args) {
4191
4291
  }
4192
4292
  }
4193
4293
 
4194
- async function cmdDocsSite() {
4195
- const { buildFumadocsReference } = await import('../lib/auto-docs.mjs');
4294
+ async function cmdDocsSite(args = []) {
4295
+ const check = args.includes('--check');
4296
+ const { buildFumadocsReference, checkFumadocsReferenceDrift } = await import('../lib/auto-docs.mjs');
4297
+ if (check) {
4298
+ const { drift } = checkFumadocsReferenceDrift({ rootDir: ROOT_DIR });
4299
+ if (drift.length === 0) {
4300
+ ok('docs/reference/ is up to date.');
4301
+ return;
4302
+ }
4303
+ println('');
4304
+ println('Generated reference docs are out of date. Run `construct docs:site` to regenerate:');
4305
+ for (const f of drift) println(` ✗ ${f}`);
4306
+ process.exitCode = 1;
4307
+ return;
4308
+ }
4196
4309
  const { written } = buildFumadocsReference({ rootDir: ROOT_DIR });
4197
4310
  if (written.length === 0) {
4198
4311
  ok('docs/reference/ already up to date.');
@@ -4340,7 +4453,6 @@ async function cmdCiPreview(args) {
4340
4453
  const { readFileSync, existsSync } = await import('node:fs');
4341
4454
  const { join } = await import('node:path');
4342
4455
  const { spawnSync: spSync } = await import('node:child_process');
4343
- const YAML = await import('node:fs');
4344
4456
 
4345
4457
  const listOnly = args.includes('--list');
4346
4458
  const fullMode = args.includes('--full');
@@ -4838,7 +4950,7 @@ async function cmdTags(args) {
4838
4950
 
4839
4951
  async function cmdScheduler(args) {
4840
4952
  const sub = args[0];
4841
- const { registerJob, runJobOnce, listJobs } = await import('../lib/scheduler/index.mjs');
4953
+ const { runJobOnce, listJobs } = await import('../lib/scheduler/index.mjs');
4842
4954
 
4843
4955
  if (!sub || sub === 'list') {
4844
4956
  const jobs = listJobs();
@@ -4876,18 +4988,30 @@ async function cmdScheduler(args) {
4876
4988
 
4877
4989
  async function cmdCreds(args) {
4878
4990
  const sub = args[0];
4879
- const { readCreds, writeCreds, deleteCreds, listCreds, checkCredsFileMode, credsFilePath } =
4991
+ const { writeCreds, deleteCreds, listCreds, checkCredsFileMode, credsFilePath } =
4880
4992
  await import('../lib/providers/creds.mjs');
4881
4993
  const { createInterface } = await import('node:readline');
4882
4994
 
4883
4995
  if (!sub || sub === 'list') {
4884
4996
  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 || '—'}`);
4997
+ if (entries.length === 0) {
4998
+ println('No provider credentials stored in the rotation store.');
4999
+ } else {
5000
+ println('Provider credentials\n');
5001
+ println(' PROVIDER ACCOUNT ROTATED_AT NEXT_ROTATION');
5002
+ for (const e of entries) {
5003
+ println(` ${e.provider.padEnd(26)} ${(e.account || '—').padEnd(27)} ${(e.rotatedAt || '—').padEnd(23)} ${e.nextRotationDue || '—'}`);
5004
+ }
4890
5005
  }
5006
+ try {
5007
+ const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
5008
+ const { providers } = getProviderModelCatalog({ env: process.env });
5009
+ println('\nLLM provider readiness (op:// references resolve at call time)\n');
5010
+ for (const p of providers) {
5011
+ println(` ${p.configured ? '✓' : '·'} ${p.id}`);
5012
+ }
5013
+ println('\n Set keys as values or op:// refs in ~/.construct/config.env; run `construct creds login copilot` for GitHub Copilot.');
5014
+ } catch { /* readiness view is informational */ }
4891
5015
  return;
4892
5016
  }
4893
5017
 
@@ -4926,7 +5050,68 @@ async function cmdCreds(args) {
4926
5050
  return;
4927
5051
  }
4928
5052
 
4929
- errorln(`Unknown creds subcommand: ${sub}. Available: list, set, rotate, revoke, test`);
5053
+ if (sub === 'login') {
5054
+ const provider = args[1] || 'copilot';
5055
+ if (provider !== 'copilot' && provider !== 'github-copilot') {
5056
+ errorln(`\`creds login\` currently supports only \`copilot\`. For API-key providers use \`construct creds set ${provider}\`.`);
5057
+ process.exit(1);
5058
+ }
5059
+ const copilot = await import('../lib/providers/copilot-auth.mjs');
5060
+ if (copilot.hasStoredCredential() && !args.includes('--force')) {
5061
+ try {
5062
+ await copilot.getCopilotToken();
5063
+ println('GitHub Copilot is already authenticated and the session token exchanged successfully.');
5064
+ println('Re-run with --force to authenticate a different account.');
5065
+ return;
5066
+ } catch {
5067
+ println('A stored Copilot credential exists but the session exchange failed; re-authenticating.');
5068
+ }
5069
+ }
5070
+ let device;
5071
+ try {
5072
+ device = await copilot.requestDeviceCode({});
5073
+ } catch (err) {
5074
+ errorln(err.message);
5075
+ process.exit(1);
5076
+ }
5077
+ println('\nGitHub Copilot sign-in');
5078
+ println(` 1. Open: ${device.verificationUri}`);
5079
+ println(` 2. Enter code: ${device.userCode}`);
5080
+ println('\nWaiting for authorization...');
5081
+ let token;
5082
+ try {
5083
+ token = await copilot.pollForAccessToken({
5084
+ deviceCode: device.deviceCode,
5085
+ interval: device.interval,
5086
+ expiresIn: device.expiresIn,
5087
+ onPending: () => process.stdout.write('.'),
5088
+ });
5089
+ process.stdout.write('\n');
5090
+ } catch (err) {
5091
+ process.stdout.write('\n');
5092
+ errorln(err.message);
5093
+ process.exit(1);
5094
+ }
5095
+ copilot.persistOAuth({ accessToken: token.accessToken, refreshToken: token.refreshToken, expiresAt: token.expiresAt, user: null });
5096
+ try {
5097
+ await copilot.getCopilotToken();
5098
+ println('GitHub Copilot authenticated. The session token exchanged successfully.');
5099
+ println('Credentials stored in ~/.construct/auth/github-copilot.json and ~/.config/github-copilot/apps.json.');
5100
+ try {
5101
+ const models = await copilot.listCopilotModels({});
5102
+ if (models.length) {
5103
+ println('\nModels available to your account (use as github-copilot/<id>):');
5104
+ for (const id of models) println(` - github-copilot/${id}`);
5105
+ }
5106
+ } catch { /* model listing is informational; auth already succeeded */ }
5107
+ } catch (err) {
5108
+ errorln(`Authenticated with GitHub, but the Copilot token exchange failed: ${err.message}`);
5109
+ process.exit(1);
5110
+ }
5111
+ return;
5112
+ }
5113
+
5114
+ errorln(`Unknown creds subcommand: ${sub}. Available: list, login, set, rotate, revoke, test`);
4930
5115
  process.exit(1);
4931
5116
  }
4932
5117
 
@@ -5149,7 +5334,7 @@ async function cmdTelemetryQuery(args) {
5149
5334
  println(' errors [--agent=<name>] [--since=24h] error span summary');
5150
5335
  println(' trace <traceId> full span tree');
5151
5336
  println('');
5152
- println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.mdx');
5337
+ println('For Langfuse/Honeycomb/Grafana Tempo/Datadog, see docs/concepts/observability.md');
5153
5338
  }
5154
5339
 
5155
5340
  async function cmdHook(args) {
@@ -5209,11 +5394,11 @@ async function readStdin() {
5209
5394
 
5210
5395
  const handlers = new Map([
5211
5396
  // Core
5212
- ['dev', cmdUp],
5213
- ['dashboard', cmdServe],
5214
- ['stop', cmdDown],
5397
+ ['dev', cmdDev],
5398
+ ['dashboard', cmdDashboard],
5399
+ ['stop', cmdStop],
5215
5400
  ['status', cmdStatus],
5216
- ['install', cmdSetup],
5401
+ ['install', cmdInstall],
5217
5402
  ['config', cmdConfig],
5218
5403
  ['intake', cmdIntake],
5219
5404
  ['recommendations', cmdRecommendations],
@@ -5233,13 +5418,18 @@ const handlers = new Map([
5233
5418
  ['distill', cmdDistill],
5234
5419
  ['ingest', cmdIngest],
5235
5420
  ['export', cmdExport],
5421
+ ['publish', cmdPublish],
5422
+ ['tools', cmdTools],
5236
5423
  ['infer', cmdInfer],
5237
5424
  ['search', cmdSearch],
5238
5425
  ['storage', cmdStorage],
5239
5426
  ['registry:status', cmdRegistryStatus],
5427
+ ['registry:validate', cmdRegistryValidate],
5428
+ ['registry:generate-docs', cmdRegistryGenerateDocs],
5429
+ ['rules', cmdRulesUsage],
5240
5430
  // Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
5241
5431
  // catalog, and per-turn accounting still run, but no CLI surface exposes
5242
- // them to the user. Handlers (cmdPricing, cmdCosts, cmdCost) remain in this
5432
+ // them to the user. Handlers (_cmdPricing, _cmdCosts, _cmdCost) remain in this
5243
5433
  // file so the OTel + dashboard wiring (planned in Workstream J) can rewire
5244
5434
  // the surface without re-implementing the logic.
5245
5435
  ['overrides', cmdOverrides],
@@ -5262,7 +5452,7 @@ const handlers = new Map([
5262
5452
  if (sub === 'verify') return cmdDocsVerify(rest);
5263
5453
  if (sub === 'update') return cmdDocsUpdate(rest);
5264
5454
  if (sub === 'check') return cmdDocsCheck(rest);
5265
- if (sub === 'site') return cmdDocsSite();
5455
+ if (sub === 'site') return cmdDocsSite(rest);
5266
5456
  if (sub === 'reconcile') return cmdDocsReconcile(rest);
5267
5457
  errorln(`Unknown docs subcommand: ${sub}. Available: check, verify, update, site, reconcile`);
5268
5458
  process.exit(1);
@@ -5277,6 +5467,10 @@ const handlers = new Map([
5277
5467
  const { runAcpServer } = await import('../lib/acp/server.mjs');
5278
5468
  runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
5279
5469
  }],
5470
+ ['chat', async (args) => {
5471
+ const { runChat } = await import('../lib/chat/cli.mjs');
5472
+ process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
5473
+ }],
5280
5474
  ['beads:stats', async (args) => {
5281
5475
  const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
5282
5476
  const jsonOutput = args.includes('--json');
@@ -5438,11 +5632,23 @@ const handlers = new Map([
5438
5632
  ['audit', async (args) => {
5439
5633
  const sub = args[0];
5440
5634
  if (sub === 'skills') return runAuditSkillsCli(args.slice(1));
5635
+ if (sub === 'specialists') return runAuditSpecialistsCli(args.slice(1));
5636
+ if (sub === 'tests') {
5637
+ const rest = args.slice(1);
5638
+ if (rest.includes('--corpus')) return runCorpusInventoryAuditCli(rest.filter((arg) => arg !== '--corpus'), { rootDir: ROOT_DIR });
5639
+ return runCapabilityLedgerAuditCli(rest, { rootDir: ROOT_DIR });
5640
+ }
5441
5641
  if (sub === 'trail' || !sub) {
5442
5642
  const { runAuditTrailCli } = await import('../lib/audit-trail.mjs');
5443
5643
  return runAuditTrailCli(args.slice(sub === 'trail' ? 1 : 0));
5444
5644
  }
5445
- errorln(`Unknown audit subcommand: ${sub}. Available: skills, trail`);
5645
+ errorln(`Unknown audit subcommand: ${sub}. Available: skills, specialists, tests, trail`);
5646
+ process.exit(1);
5647
+ }],
5648
+ ['artifact', async (args) => {
5649
+ const sub = args[0];
5650
+ if (sub === 'validate') return runArtifactValidateCli(args.slice(1));
5651
+ errorln(`Unknown artifact subcommand: ${sub || '(none)'}. Available: validate`);
5446
5652
  process.exit(1);
5447
5653
  }],
5448
5654
  ['doc', async (args) => {
@@ -5838,6 +6044,14 @@ const handlers = new Map([
5838
6044
  const { runWireframeCli } = await import('../lib/wireframe.mjs');
5839
6045
  return runWireframeCli(args);
5840
6046
  }],
6047
+ ['diagram', async (args) => {
6048
+ const { runDiagramCli } = await import('../lib/diagram.mjs');
6049
+ return runDiagramCli(args);
6050
+ }],
6051
+ ['demo', async (args) => {
6052
+ const { runDemoCli } = await import('../lib/demo.mjs');
6053
+ return runDemoCli(args);
6054
+ }],
5841
6055
  ['skills', async (args) => {
5842
6056
  const sub = args[0];
5843
6057
  if (sub === 'scope' || !sub) {
@@ -5882,6 +6096,30 @@ const handlers = new Map([
5882
6096
  const { runEmbedCli } = await import('../lib/embed/cli.mjs');
5883
6097
  return runEmbedCli(args, { rootDir: new URL('..', import.meta.url).pathname });
5884
6098
  }],
6099
+ ['oracle', async (args) => {
6100
+ const { runOracleCli } = await import('../lib/oracle/cli.mjs');
6101
+ return runOracleCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd(), homeDir: HOME });
6102
+ }],
6103
+ ['certify', async (args) => {
6104
+ const { runCertificationCli } = await import('../lib/certification/cli.mjs');
6105
+ const code = await runCertificationCli(args, { projectDir: process.cwd(), repoRoot: ROOT_DIR });
6106
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6107
+ }],
6108
+ ['improvement', async (args) => {
6109
+ const { runImprovementCli } = await import('../lib/improvement/cli.mjs');
6110
+ const code = await runImprovementCli(args, { projectDir: process.cwd() });
6111
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6112
+ }],
6113
+ ['matrix', async (args) => {
6114
+ const { runGraphCli } = await import('../lib/graph/cli.mjs');
6115
+ const code = runGraphCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd() });
6116
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6117
+ }],
6118
+ ['impact', async (args) => {
6119
+ const { runImpactCli } = await import('../lib/graph/impact-cli.mjs');
6120
+ const code = await runImpactCli(args, { rootDir: ROOT_DIR, projectDir: process.cwd() });
6121
+ if (typeof code === 'number' && code !== 0) process.exit(code);
6122
+ }],
5885
6123
  ['reflect', async (args) => {
5886
6124
  const { runReflectCli } = await import('../lib/reflect.mjs');
5887
6125
  return runReflectCli(args);
@@ -6355,6 +6593,67 @@ if (command === '--version' || command === '-V') {
6355
6593
  process.exit(0);
6356
6594
  }
6357
6595
 
6596
+ // Unknown-flag guidance for commands that fully declare their flags (strictFlags
6597
+ // in the spec). A stray or typo'd flag on these is a user error, not a no-op:
6598
+ // reject it with the nearest declared flag and the command's help, matching how
6599
+ // install (lib/setup.mjs KNOWN_FLAGS) already behaves, so no flag is silently
6600
+ // swallowed. Opt-in keeps commands that accept undeclared flags unaffected.
6601
+
6602
+ function validateKnownFlags(name, argv) {
6603
+ const spec = CLI_COMMANDS.find((c) => c.name === name);
6604
+ if (!spec || !spec.strictFlags) return;
6605
+
6606
+ const exact = new Set(['--help', '-h']);
6607
+ const prefixes = [];
6608
+ for (const opt of spec.options || []) {
6609
+ for (const part of String(opt.flag).split(',')) {
6610
+ const token = part.trim().split('=')[0].split(/\s/)[0];
6611
+ if (!token.startsWith('-')) continue;
6612
+ if (/-<[^>]+>$/.test(token)) prefixes.push(token.replace(/<[^>]+>$/, ''));
6613
+ else exact.add(token);
6614
+ }
6615
+ }
6616
+
6617
+ const declared = [...exact].filter((f) => f.startsWith('--'));
6618
+ const dist = (a, b) => {
6619
+ const row = Array(b.length + 1).fill(0).map((_, i) => i);
6620
+ for (let i = 1; i <= a.length; i += 1) {
6621
+ let prev = i;
6622
+ for (let j = 1; j <= b.length; j += 1) {
6623
+ const cur = a[i - 1] === b[j - 1] ? row[j - 1] : 1 + Math.min(row[j - 1], row[j], prev);
6624
+ row[j - 1] = prev;
6625
+ prev = cur;
6626
+ }
6627
+ row[b.length] = prev;
6628
+ }
6629
+ return row[b.length];
6630
+ };
6631
+
6632
+ const unknown = [];
6633
+ for (const tok of argv) {
6634
+ if (tok === '--') break;
6635
+ if (!/^--?[A-Za-z]/.test(tok)) continue;
6636
+ const bare = tok.split('=')[0];
6637
+ if (exact.has(bare) || prefixes.some((p) => bare.startsWith(p))) continue;
6638
+ unknown.push(bare);
6639
+ }
6640
+ if (unknown.length === 0) return;
6641
+
6642
+ for (const flag of unknown) {
6643
+ errorln(`Unknown flag: ${flag}`);
6644
+ let best = 4;
6645
+ let suggestion = null;
6646
+ for (const cand of declared) {
6647
+ const d = dist(flag, cand);
6648
+ if (d < best) { best = d; suggestion = cand; }
6649
+ }
6650
+ if (suggestion) errorln(`Did you mean: ${suggestion}?`);
6651
+ }
6652
+ println('');
6653
+ println(`${COLORS.dim}Run 'construct ${name} --help' for valid options${COLORS.reset}`);
6654
+ process.exit(1);
6655
+ }
6656
+
6358
6657
  const handler = handlers.get(command);
6359
6658
  if (!handler) {
6360
6659
  errorln(`Unknown command: ${command}`);
@@ -6433,7 +6732,9 @@ if (command !== 'hook') {
6433
6732
 
6434
6733
  const probe = await maybeFirstInvocationProbe({ command, homeDir: HOME, env: process.env });
6435
6734
  if (probe?.runSetup) {
6436
- await cmdSetup([]);
6735
+ await cmdInstall([]);
6437
6736
  }
6438
6737
 
6738
+ validateKnownFlags(command, rest);
6739
+
6439
6740
  await handler(rest);