@geraldmaron/construct 1.2.2 → 1.3.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 (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
package/bin/construct CHANGED
@@ -15,7 +15,7 @@ import { spawnSync } from 'node:child_process';
15
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, setModelWithTierInference } from '../lib/model-router.mjs';
18
+ import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig, applyToEnv, resetEnv, setModelWithTierInference, listAvailableModels } 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';
@@ -64,15 +64,20 @@ import { runCapabilityLedgerAuditCli } from '../lib/capability-ledger.mjs';
64
64
  import { runCorpusInventoryAuditCli } from '../lib/test-corpus-inventory.mjs';
65
65
  import { runArtifactValidateCli } from '../lib/artifact-release-gate.mjs';
66
66
  import { runTeamReviewCli } from '../lib/telemetry/team-rollup.mjs';
67
- import { startDashboard, startServices } from '../lib/service-manager.mjs';
67
+ import { startServices } from '../lib/service-manager.mjs';
68
68
  import { readCostLog, summarizeCostData, formatCostReport, clearCostLog } from '../lib/cost.mjs';
69
69
  import { readEfficiencyLog, summarizeEfficiencyData, formatEfficiencyReport } from '../lib/efficiency.mjs';
70
- import { resolveColors } from '../lib/term-format.mjs';
70
+ import { resolveUiColors } from '../lib/ui/theme.mjs';
71
+ import { UNICODE_GLYPHS, ASCII_GLYPHS } from '../lib/ui/glyphs.mjs';
72
+ import { formatPathLink, terminalLinksEnabled } from '../lib/ui/links.mjs';
73
+ import { ok as uiOk, warn as uiWarn, info as uiInfo, fail as uiFail, section as uiSection } from '../lib/ui/components.mjs';
74
+ import { getInstalledVersion } from '../lib/version.mjs';
71
75
  import { buildHybridSearchResultsAsync } from '../lib/storage/hybrid-query.mjs';
72
76
  import { createSqlClient, closeSqlClient } from '../lib/storage/backend.mjs';
73
77
  import { syncFileStateToSql } from '../lib/storage/sync.mjs';
74
78
  import { deleteIngestedArtifacts, getStorageStatus, inferProjectName, resetStorage } from '../lib/storage/admin.mjs';
75
79
  import { runPressureRelease } from '../lib/runtime-pressure.mjs';
80
+ import { loadRegistry, listSpecialists } from '../lib/registry/loader.mjs';
76
81
 
77
82
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
78
83
  const HOME = os.homedir();
@@ -82,13 +87,21 @@ for (const [key, value] of Object.entries(ENV)) {
82
87
  if (!(key in process.env)) process.env[key] = value;
83
88
  }
84
89
 
85
- const COLORS = resolveColors();
90
+ const COLORS = resolveUiColors();
91
+ const GLYPHS = process.env.CX_ASCII === '1' ? ASCII_GLYPHS : UNICODE_GLYPHS;
92
+ const LINKS_ON = terminalLinksEnabled(process.env, { stream: process.stdout });
86
93
 
87
94
  function println(message = '') { process.stdout.write(`${message}\n`); }
88
95
  function errorln(message = '') { process.stderr.write(`${message}\n`); }
89
- function ok(message) { println(` ${COLORS.green}✓${COLORS.reset} ${message}`); }
90
- function warn(message) { println(` ${COLORS.yellow}⚠${COLORS.reset} ${message}`); }
91
- function info(message) { println(` ${COLORS.cyan}→${COLORS.reset} ${message}`); }
96
+ function ok(message) { println(uiOk(message, { colors: COLORS, glyphs: GLYPHS })); }
97
+ function warn(message) { println(uiWarn(message, { colors: COLORS })); }
98
+ function info(message) { println(uiInfo(message, { colors: COLORS, glyphs: GLYPHS })); }
99
+
100
+ // A repo path becomes an OSC-8 hyperlink where the terminal honors them; the
101
+ // visible label stays the raw path so Terminal.app's own Cmd-click detection
102
+ // still resolves it where OSC-8 is ignored.
103
+
104
+ function linkPath(p) { return formatPathLink(p, COLORS, { enabled: LINKS_ON }); }
92
105
 
93
106
  // VS16-suffixed emojis (e.g. ⌨️, 🛠️, ⬆️) come from BMP "text symbol" Unicode
94
107
  // blocks and are commonly rendered as 1 visual column in macOS Terminal /
@@ -108,58 +121,34 @@ function usage(opts = {}) {
108
121
  const visible = CLI_COMMANDS.filter((c) => !c.internal);
109
122
  const commands = showAll ? visible : visible.filter((c) => c.core);
110
123
 
111
- if (showAll) {
112
- println(`${COLORS.bold}construct${COLORS.reset} — AI agent harness (all commands)`);
113
- } else {
114
- println(`${COLORS.bold}construct${COLORS.reset} — AI agent harness`);
115
- println(`${COLORS.dim}Tip: Run ${COLORS.reset}construct --all${COLORS.dim} to see all commands · ${COLORS.reset}construct <cmd> --help${COLORS.dim} for details${COLORS.reset}`);
124
+ const ver = getInstalledVersion().version;
125
+ const wordmark = `${COLORS.brandAccent}${COLORS.bold}construct${COLORS.reset}`;
126
+ const tagline = showAll ? 'AI agent harness · all commands' : 'AI agent harness';
127
+ println(`\n${wordmark} ${COLORS.dim}v${ver}${COLORS.reset} ${COLORS.muted}${tagline}${COLORS.reset}`);
128
+ if (!showAll) {
129
+ println(`${COLORS.dim}Tip: ${COLORS.reset}construct --all${COLORS.dim} lists every command · ${COLORS.reset}construct <cmd> --help${COLORS.dim} for details${COLORS.reset}`);
116
130
  }
117
131
 
118
- println(`\n${COLORS.dim}Usage:${COLORS.reset} construct <command> [options]\n`);
132
+ println(`\n${COLORS.dim}Usage:${COLORS.reset} construct <command> [options]`);
133
+ println(`${COLORS.dim}OpenCode is the primary conversation surface. Run ${COLORS.reset}construct sync${COLORS.dim} to install or refresh editor adapters.${COLORS.reset}`);
134
+
135
+ // The name column sizes to the longest visible command (capped) so long names
136
+ // are never truncated and descriptions stay aligned regardless of the set shown.
137
+
138
+ const nameWidth = Math.min(18, Math.max(...commands.map((c) => c.name.length)));
119
139
 
120
140
  for (const category of CATEGORY_ORDER) {
121
141
  const categoryCommands = commands.filter((c) => c.category === category);
122
142
  if (!categoryCommands.length) continue;
123
- println(`${COLORS.bold}${category}${COLORS.reset}`);
143
+ println(`\n${uiSection(category, { colors: COLORS })}`);
124
144
  for (const command of categoryCommands) {
125
145
  const emoji = command.emoji ?? ' ';
126
- println(` ${iconColumn(emoji)}${command.name.padEnd(14)} ${command.description}`);
146
+ const name = `${COLORS.bold}${command.name.padEnd(nameWidth)}${COLORS.reset}`;
147
+ println(` ${iconColumn(emoji)}${name} ${command.description}`);
127
148
  }
128
- println('');
129
149
  }
130
- }
131
150
 
132
- // Bare-command landing — show the same core-command listing as `construct
133
- // --help`, prefixed with a context line so users know where they are, and
134
- // suffixed with the one next-step suggestion that applies to their state.
135
-
136
- async function showInteractiveMenu() {
137
- const { existsSync } = await import('node:fs');
138
- const { join } = await import('node:path');
139
-
140
- const projectRoot = process.cwd();
141
- const isConstructProject = existsSync(join(projectRoot, 'construct.config.json')) ||
142
- existsSync(join(projectRoot, '.cx'));
143
-
144
- if (isConstructProject) {
145
- const projectName = process.env.CX_PROJECT_NAME || path.basename(projectRoot);
146
- println(`${COLORS.dim}Project:${COLORS.reset} ${projectName}`);
147
- println('');
148
- }
149
-
150
- usage({ all: false });
151
-
152
- if (isConstructProject) {
153
- const { readDashboardState } = await import('../lib/service-manager.mjs');
154
- const dashboard = readDashboardState(HOME);
155
- if (!dashboard) {
156
- println(`${COLORS.yellow}💡 Services not running. Start with:${COLORS.reset} ${COLORS.green}construct dev${COLORS.reset}`);
157
- println('');
158
- }
159
- } else {
160
- println(`${COLORS.yellow}💡 Not a Construct project. Initialize with:${COLORS.reset} ${COLORS.green}construct init${COLORS.reset}`);
161
- println('');
162
- }
151
+ println(`\n${COLORS.dim}Docs:${COLORS.reset} ${linkPath('docs/guides/reference/cli/')}`);
163
152
  }
164
153
 
165
154
  function runNodeScript(scriptPath, args = [], extraEnv = {}, { exitOnError = true } = {}) {
@@ -190,6 +179,42 @@ async function cmdRegistryGenerateDocs(args = []) {
190
179
  if (code) process.exit(code);
191
180
  }
192
181
 
182
+ async function cmdRegistryDiff(args = []) {
183
+ const { runRegistryDiff } = await import('../lib/registry/cli.mjs');
184
+ const code = await runRegistryDiff(args, { rootDir: ROOT_DIR, println, errorln });
185
+ if (code) process.exit(code);
186
+ }
187
+
188
+ async function cmdRegistryPrune(args = []) {
189
+ const { runRegistryPrune } = await import('../lib/registry/cli.mjs');
190
+ const code = await runRegistryPrune(args, { rootDir: ROOT_DIR, println, errorln });
191
+ if (code) process.exit(code);
192
+ }
193
+
194
+ async function cmdTeamAdd(args = []) {
195
+ const { runTeamAdd } = await import('../lib/registry/cli.mjs');
196
+ const code = await runTeamAdd(args, { rootDir: ROOT_DIR, println, errorln });
197
+ if (code) process.exit(code);
198
+ }
199
+
200
+ async function cmdTeamRemove(args = []) {
201
+ const { runTeamRemove } = await import('../lib/registry/cli.mjs');
202
+ const code = await runTeamRemove(args, { rootDir: ROOT_DIR, println, errorln });
203
+ if (code) process.exit(code);
204
+ }
205
+
206
+ async function cmdSpecialistAdd(args = []) {
207
+ const { runSpecialistAdd } = await import('../lib/registry/cli.mjs');
208
+ const code = await runSpecialistAdd(args, { rootDir: ROOT_DIR, println, errorln });
209
+ if (code) process.exit(code);
210
+ }
211
+
212
+ async function cmdSpecialistRemove(args = []) {
213
+ const { runSpecialistRemove } = await import('../lib/registry/cli.mjs');
214
+ const code = await runSpecialistRemove(args, { rootDir: ROOT_DIR, println, errorln });
215
+ if (code) process.exit(code);
216
+ }
217
+
193
218
  async function cmdRulesUsage(args = []) {
194
219
  const { summarizeRuleCalls } = await import('../lib/telemetry/rule-calls.mjs');
195
220
  const { summarizeHookCalls } = await import('../lib/telemetry/hook-calls.mjs');
@@ -235,12 +260,12 @@ async function cmdStatus() {
235
260
  // to "is this thing actually learning?" without remembering a second
236
261
  // command. Cheap: just reads .cx/observations + .cx/outcomes summaries.
237
262
  try {
238
- const { resolveActiveProfile } = await import('../lib/profiles/loader.mjs');
263
+ const { resolveActiveScope } = await import('../lib/scopes/loader.mjs');
239
264
  const { readSummary } = await import('../lib/outcomes/aggregate.mjs');
240
265
  const fs = await import('node:fs');
241
266
  const path = await import('node:path');
242
267
  const cwd = process.cwd();
243
- const active = resolveActiveProfile(cwd);
268
+ const active = resolveActiveScope(cwd);
244
269
  const obsIdxPath = path.join(cwd, '.cx', 'observations', 'index.json');
245
270
  let obsTotal = 0;
246
271
  let obs24h = 0;
@@ -349,15 +374,13 @@ async function cmdCompletions(args) {
349
374
  }
350
375
 
351
376
  async function cmdList() {
352
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
377
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
353
378
  println('Construct Agents\n');
354
- println('Personas:');
355
- for (const persona of registry.personas ?? []) {
356
- println(` ${persona.displayName} [${persona.role}] ${persona.description}`);
357
- }
358
- println('\nSpecialists:');
359
- for (const agent of registry.agents ?? []) {
360
- println(` ${registry.prefix}-${agent.name} ${agent.description}`);
379
+ println('Specialists:');
380
+ const specialists = Object.values(registry.specialists ?? {});
381
+ for (const specialist of specialists) {
382
+ const id = specialist.name.startsWith('cx-') ? specialist.name : `cx-${specialist.name}`;
383
+ println(` ${id} ${specialist.displayName || ''}`);
361
384
  }
362
385
  }
363
386
 
@@ -503,18 +526,9 @@ async function cmdDoctor() {
503
526
  const add = (label, pass, optional = false) => checks.push({ label, pass, optional });
504
527
  const { isConstructSourceCheckout } = await import('../lib/doctor/source-checkout.mjs');
505
528
  const isSourceCheckout = isConstructSourceCheckout(ROOT_DIR);
506
- add('registry.json exists', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'registry.json')));
529
+ add('specialists/org exists', fs.existsSync(path.join(ROOT_DIR, 'specialists', 'org')));
507
530
  add('sync-specialists.mjs exists', fs.existsSync(path.join(ROOT_DIR, 'scripts', 'sync-specialists.mjs')));
508
531
 
509
- // Dashboard static build output is absent from published npm installs.
510
-
511
- if (isSourceCheckout) {
512
- const dashboardIndex = path.join(ROOT_DIR, 'lib', 'server', 'static', 'index.html');
513
- add('Dashboard built (lib/server/static/index.html)', fs.existsSync(dashboardIndex));
514
- } else {
515
- add('Dashboard built (skipped — published package)', true, true);
516
- }
517
-
518
532
  // Project .gitignore must cover .cx/. Auto-fix via --fix-gitignore.
519
533
  // Only checks when cwd looks like a Construct project (has .cx/ or
520
534
  // .construct/); non-project cwds get no warning.
@@ -542,8 +556,8 @@ async function cmdDoctor() {
542
556
  // name across Claude / Codex / Copilot user-scope dirs.
543
557
 
544
558
  const userHome = HOME;
545
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
546
- const specialistSet = new Set((registry.specialists ?? []).map((s) => `cx-${s.name}`));
559
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
560
+ const specialistSet = new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
547
561
  const legacyTargets = [
548
562
  { dir: path.join(userHome, '.claude', 'agents'), ext: '.md' },
549
563
  { dir: path.join(userHome, '.codex', 'agents'), ext: '.toml' },
@@ -653,6 +667,26 @@ async function cmdDoctor() {
653
667
  );
654
668
  }
655
669
 
670
+ // Mirror model tier overrides stranded in the pre-XDG legacy config forward
671
+ // before resolving tiers, so an install upgraded across the XDG move reports
672
+ // configured without a manual copy. Applied to process.env too so the check
673
+ // below sees the just-migrated values in the same run.
674
+ try {
675
+ const { migrateLegacyModelConfig, migrateLegacyCredentialConfig } = await import('../lib/config/legacy-config-migration.mjs');
676
+ const migration = migrateLegacyModelConfig({ homeDir: HOME });
677
+ if (migration.performed) {
678
+ Object.assign(process.env, migration.migrated);
679
+ println(` fix: migrated ${Object.keys(migration.migrated).join(', ')} from ${migration.legacyPath} → ${migration.xdgPath}`);
680
+ }
681
+ const credentialMigration = migrateLegacyCredentialConfig({ homeDir: HOME });
682
+ if (credentialMigration.performed) {
683
+ Object.assign(process.env, credentialMigration.migrated);
684
+ println(` fix: migrated credentials ${Object.keys(credentialMigration.migrated).join(', ')} from ${credentialMigration.legacyPath} → ${credentialMigration.xdgPath}`);
685
+ }
686
+ } catch (err) {
687
+ add(`Models — legacy config migration failed: ${err.message}`, false, true);
688
+ }
689
+
656
690
  // Tier model selection. Construct ships with no default — at least
657
691
  // one tier must be configured (registry.json primary OR CX_MODEL_*
658
692
  // env override) before any LLM-backed workflow can run.
@@ -665,7 +699,7 @@ async function cmdDoctor() {
665
699
  if (resolved.complete && !resolved.errors) {
666
700
  add('Models — all tiers configured', true);
667
701
  } else if (resolved.configured === 0) {
668
- add('Models — no tier configured (pick one in the dashboard or run `construct models --apply`)', false, true);
702
+ add('Models — no tier configured (run `construct models --apply`)', false, true);
669
703
  } else {
670
704
  const missing = Object.entries(resolved.models)
671
705
  .filter(([, v]) => !v)
@@ -847,19 +881,28 @@ async function cmdDoctor() {
847
881
 
848
882
  // Validate promptFile references and skill bindings
849
883
  try {
850
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
851
- const brokenPromptFiles = (registry.specialists ?? []).filter((s) => {
884
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
885
+ const brokenPromptFiles = Object.values(registry.specialists ?? {}).filter((s) => {
852
886
  if (!s.promptFile) return false;
853
887
  return !fs.existsSync(path.join(ROOT_DIR, s.promptFile));
854
888
  });
855
889
  add('All specialist promptFiles resolve', brokenPromptFiles.length === 0);
856
890
  const headhuntClassifierAgents = ['cx-docs-keeper', 'cx-researcher', 'cx-architect', 'cx-security', 'cx-engineer'];
857
- const registryNames = new Set((registry.specialists ?? []).map((s) => `cx-${s.name}`));
891
+ const registryNames = new Set(Object.values(registry.specialists ?? {}).map((s) => `cx-${s.name}`));
858
892
  const classifierOrphans = headhuntClassifierAgents.filter((n) => !registryNames.has(n));
859
893
  add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
860
894
  const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
861
895
  add('No declared skills missing on disk', skillAudit.pass, false);
862
896
  if (isSourceCheckout) {
897
+ const { scanRetiredRegistryPathReferences } = await import('../lib/registry/retired-paths.mjs');
898
+ const staleRegistryPaths = scanRetiredRegistryPathReferences(ROOT_DIR);
899
+ add(
900
+ staleRegistryPaths.length === 0
901
+ ? 'No retired registry path references'
902
+ : `Retired registry path references (${staleRegistryPaths.length}: ${staleRegistryPaths.slice(0, 3).map((h) => h.file).join(', ')}${staleRegistryPaths.length > 3 ? ', …' : ''})`,
903
+ staleRegistryPaths.length === 0,
904
+ false,
905
+ );
863
906
  const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
864
907
  add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
865
908
 
@@ -904,7 +947,8 @@ async function cmdDoctor() {
904
947
  if (fs.existsSync(beadsPreCommit) && !inCI) {
905
948
  const hp = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd: ROOT_DIR, encoding: 'utf8' });
906
949
  const value = hp.status === 0 ? (hp.stdout || '').trim() : '';
907
- const wired = value === '.beads/hooks';
950
+ const absEquiv = path.join(ROOT_DIR, '.beads', 'hooks');
951
+ const wired = value === '.beads/hooks' || value === absEquiv;
908
952
  const label = wired
909
953
  ? 'Git hooks wired (core.hooksPath = .beads/hooks)'
910
954
  : value
@@ -1407,7 +1451,6 @@ async function cmdDev(args = []) {
1407
1451
  }
1408
1452
 
1409
1453
  println('');
1410
- println('Dashboard: http://127.0.0.1:4242');
1411
1454
  println('Run \x1b[32mconstruct\x1b[0m for commands');
1412
1455
  println('');
1413
1456
 
@@ -1434,29 +1477,6 @@ async function cmdStop() {
1434
1477
  } catch { /* best effort */ }
1435
1478
  }
1436
1479
 
1437
- async function cmdDashboard() {
1438
- if (restArgsCache.includes('--token')) {
1439
- const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
1440
- const existing = getDashboardToken();
1441
- if (existing) {
1442
- println(`Dashboard token already set. To rotate it, remove CONSTRUCT_DASHBOARD_TOKEN from ~/.construct/config.env and re-run.`);
1443
- return;
1444
- }
1445
- const token = generateToken();
1446
- setDashboardToken(token);
1447
- println(`Dashboard token generated and saved to ~/.construct/config.env`);
1448
- println(`CONSTRUCT_DASHBOARD_TOKEN=${token}`);
1449
- println(`Pass this token in the dashboard login prompt or as: Authorization: Bearer <token>`);
1450
- return;
1451
- }
1452
- const dashboard = await startDashboard({ rootDir: ROOT_DIR, homeDir: HOME });
1453
- if (dashboard.reused) {
1454
- println(`Construct dashboard already running on ${dashboard.url} (pid ${dashboard.pid})`);
1455
- } else {
1456
- println(`Started Construct dashboard on ${dashboard.url} (pid ${dashboard.pid})`);
1457
- }
1458
- }
1459
-
1460
1480
  async function cmdVersion() {
1461
1481
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
1462
1482
  println(`construct v${pkg.version}`);
@@ -1471,9 +1491,7 @@ async function cmdMigrate(args) {
1471
1491
  const candidates = pathArg
1472
1492
  ? [pathArg]
1473
1493
  : [
1474
- path.join(ROOT_DIR, 'specialists', 'registry.json'),
1475
- path.join(ROOT_DIR, 'specialists', 'contracts.json'),
1476
- path.join(ROOT_DIR, 'specialists', 'role-manifests.json'),
1494
+ path.join(ROOT_DIR, 'specialists', 'unified-registry.json'),
1477
1495
  path.join(process.cwd(), '.cx', 'config.json'),
1478
1496
  ];
1479
1497
 
@@ -1600,7 +1618,7 @@ async function cmdExport(args) {
1600
1618
  inputPath: resolvedIn,
1601
1619
  outputPath: resolvedOut,
1602
1620
  format: toFlag,
1603
- figures: toFlag === 'pdf' ? figures : false,
1621
+ figures,
1604
1622
  branding,
1605
1623
  repoRoot: ROOT_DIR,
1606
1624
  });
@@ -1620,19 +1638,17 @@ async function cmdPublish(args) {
1620
1638
  async function cmdTools(args) {
1621
1639
  const [sub, ...rest] = args;
1622
1640
  if (sub !== 'detect') {
1623
- errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME] [--dashboard-demo=NAME]');
1641
+ errorln('Usage: construct tools detect [--json] [--figures] [--no-figures] [--demo=NAME]');
1624
1642
  process.exit(1);
1625
1643
  }
1626
1644
  const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
1627
1645
  const json = rest.includes('--json');
1628
1646
  const figures = !rest.includes('--no-figures');
1629
1647
  const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
1630
- const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
1631
1648
  const detection = detectPublishPipeline({
1632
1649
  format: 'pdf',
1633
1650
  includeFigures: figures,
1634
1651
  includeTerminalDemo: demos.length > 0,
1635
- includeDashboardDemo: dashboardDemos.length > 0,
1636
1652
  cwd: process.cwd(),
1637
1653
  repoRoot: ROOT_DIR,
1638
1654
  });
@@ -2091,7 +2107,7 @@ function printActiveConfig() {
2091
2107
  }
2092
2108
 
2093
2109
  async function printIntakeHelp() {
2094
- const { getRebrand } = await import('../lib/profiles/rebrand.mjs');
2110
+ const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
2095
2111
  const { intakeQueueLabel, signalNoun } = getRebrand(process.cwd());
2096
2112
  println(`Construct intake — inspect and process the ${intakeQueueLabel}.`);
2097
2113
  println('');
@@ -2209,7 +2225,7 @@ async function cmdIntake(args) {
2209
2225
  }
2210
2226
 
2211
2227
  const { createIntakeQueue } = await import('../lib/intake/queue.mjs');
2212
- const { getRebrand } = await import('../lib/profiles/rebrand.mjs');
2228
+ const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
2213
2229
  const cwd = process.cwd();
2214
2230
  const rebrand = getRebrand(cwd);
2215
2231
  const queue = createIntakeQueue(cwd, process.env);
@@ -3637,7 +3653,7 @@ async function cmdUpgrade(args) {
3637
3653
  }
3638
3654
  }
3639
3655
  async function cmdValidate() {
3640
- const reg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3656
+ const reg = loadRegistry({ rootDir: ROOT_DIR });
3641
3657
  const result = validateRegistry(reg, { rootDir: ROOT_DIR });
3642
3658
  if (result.valid) {
3643
3659
  println(`✓ Registry valid (${result.summary})`);
@@ -3680,9 +3696,9 @@ async function cmdOrchestrate(args) {
3680
3696
  // running in-process, proving the engine-as-service contract editors/CI use.
3681
3697
  if (rest.includes('--remote')) {
3682
3698
  const remoteVal = flag('--remote');
3683
- const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') : `http://${process.env.BIND_HOST || '127.0.0.1'}:${process.env.PORT || '4242'}`;
3684
- const { getDashboardToken } = await import('../lib/server/auth.mjs');
3685
- const token = getDashboardToken();
3699
+ const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') : (process.env.CONSTRUCT_ORCHESTRATION_URL || '').replace(/\/$/, '');
3700
+ if (!base) { errorln('No orchestration service configured. Pass --remote <url> or set CONSTRUCT_ORCHESTRATION_URL.'); process.exit(1); }
3701
+ const token = process.env.CONSTRUCT_ORCHESTRATION_TOKEN || process.env.CONSTRUCT_DASHBOARD_TOKEN || null;
3686
3702
  const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
3687
3703
  const positional = rest.filter((a) => !a.startsWith('--'));
3688
3704
  try {
@@ -3712,7 +3728,7 @@ async function cmdOrchestrate(args) {
3712
3728
  return;
3713
3729
  }
3714
3730
  } catch (err) {
3715
- errorln(`Could not reach the orchestration daemon at ${base}: ${err.message}. Start it with \`construct dashboard\`.`);
3731
+ errorln(`Could not reach the orchestration service at ${base}: ${err.message}. Check CONSTRUCT_ORCHESTRATION_URL.`);
3716
3732
  process.exit(1);
3717
3733
  }
3718
3734
  }
@@ -3773,7 +3789,15 @@ async function cmdOrchestrate(args) {
3773
3789
  }
3774
3790
 
3775
3791
  async function cmdModels(args) {
3776
- if (args[0] === 'resolve') {
3792
+ const envPath = getUserEnvPath(HOME);
3793
+ const sub = args[0] && !args[0].startsWith('-') ? args[0] : null;
3794
+ const KNOWN_SUBCOMMANDS = new Set(['list', 'set', 'free', 'reset', 'resolve']);
3795
+ if (sub && !KNOWN_SUBCOMMANDS.has(sub)) {
3796
+ errorln(`Unknown models subcommand: ${sub}. Run \`construct models --help\` for the supported set.`);
3797
+ process.exit(1);
3798
+ }
3799
+
3800
+ if (sub === 'resolve') {
3777
3801
  const flag = (name) => { const i = args.indexOf(name); return i !== -1 ? args[i + 1] : undefined; };
3778
3802
  const { resolveEmbeddedModel } = await import('../lib/embedded-contract/model-resolve.mjs');
3779
3803
  const { wrapContractResult } = await import('../lib/embedded-contract/envelope.mjs');
@@ -3790,21 +3814,36 @@ async function cmdModels(args) {
3790
3814
  println(JSON.stringify(envelope, null, 2));
3791
3815
  return;
3792
3816
  }
3817
+ // Legacy flag forms stay as compatibility aliases for the documented
3818
+ // subcommands. Each emits a one-line deprecation notice on stderr so stdout
3819
+ // stays clean while operators migrate to `models set|reset|free`.
3793
3820
  const tier = args.find((arg) => arg.startsWith('--tier='))?.split('=')[1] ?? '';
3794
- const setModel = args.find((arg) => arg.startsWith('--set='))?.split('=')[1] ?? '';
3795
- const envPath = getUserEnvPath(HOME);
3796
- if (args.includes('--reset')) {
3821
+ const modelFlag = args.find((arg) => arg.startsWith('--model='))?.split('=')[1];
3822
+ const legacySetFlag = args.find((arg) => arg.startsWith('--set='))?.split('=')[1];
3823
+ const setModel = modelFlag ?? legacySetFlag ?? '';
3824
+
3825
+ if (sub === 'reset' || args.includes('--reset')) {
3826
+ if (sub !== 'reset') errorln('Note: `construct models --reset` is a deprecated alias for `construct models reset`.');
3797
3827
  resetEnv(envPath);
3798
- println('Removed CX_MODEL_* overrides from ~/.construct/config.env.');
3828
+ println(`Removed CX_MODEL_* overrides from ${envPath}.`);
3799
3829
  await cmdSync([]);
3800
3830
  return;
3801
3831
  }
3802
- if (tier && setModel) {
3803
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3832
+ if (sub === 'set' || (sub === null && tier && setModel)) {
3833
+ if (!tier || !setModel) {
3834
+ errorln('Usage: construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
3835
+ process.exit(1);
3836
+ }
3837
+ if (modelFlag === undefined && legacySetFlag !== undefined) {
3838
+ errorln('Note: `--set=<model>` is a deprecated alias for `--model=<model>`.');
3839
+ }
3840
+ if (sub === null) {
3841
+ errorln('Note: `construct models --tier=… --set=…` is a deprecated alias for `construct models set --tier=… --model=…`.');
3842
+ }
3804
3843
  const preferFree = args.includes('--prefer-free');
3805
3844
  const preferFreeSameFamily = args.includes('--prefer-free-same-family');
3806
- const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
3807
- println(`Set ${tier} -> ${setModel} in ~/.construct/config.env`);
3845
+ const inferred = setModelWithTierInference(envPath, tier, setModel, {}, { preferFree, preferFreeSameFamily });
3846
+ println(`Set ${tier} -> ${setModel} in ${envPath}`);
3808
3847
  if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
3809
3848
  if (preferFree) println('Prefer-free mode: enabled');
3810
3849
  println(`Resolved tier set:`);
@@ -3814,7 +3853,8 @@ async function cmdModels(args) {
3814
3853
  await cmdSync([]);
3815
3854
  return;
3816
3855
  }
3817
- if (args.includes('--poll')) {
3856
+ if (sub === 'free' || args.includes('--poll')) {
3857
+ if (sub !== 'free') errorln('Note: `construct models --poll` is a deprecated alias for `construct models free`.');
3818
3858
  const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
3819
3859
  if (!apiKey) {
3820
3860
  errorln('Error: no OpenRouter API key found in OPENROUTER_API_KEY or OpenCode config.');
@@ -3833,15 +3873,14 @@ async function cmdModels(args) {
3833
3873
  if (args.includes('--apply')) {
3834
3874
  const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
3835
3875
  const models = await pollFreeModels(apiKey);
3836
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3837
3876
  const selections = {};
3838
3877
  for (const currentTier of ['reasoning', 'standard', 'fast']) {
3839
- const fallbacks = registry.models?.[currentTier]?.fallback ?? [];
3878
+ const fallbacks = [];
3840
3879
  const id = selectForTier(models, currentTier, fallbacks);
3841
3880
  if (id) selections[currentTier] = id;
3842
3881
  }
3843
3882
  applyToEnv(envPath, selections);
3844
- println('Written to ~/.construct/config.env. Running construct sync...');
3883
+ println(`Written to ${envPath}. Running construct sync...`);
3845
3884
  await cmdSync([]);
3846
3885
  return;
3847
3886
  }
@@ -3878,16 +3917,15 @@ async function cmdModels(args) {
3878
3917
  for (const [tier, model] of Object.entries(selections)) {
3879
3918
  println(` ${tier.padEnd(11)} ${model}`);
3880
3919
  }
3881
- println('Written to ~/.construct/config.env. Running construct sync...');
3920
+ println(`Written to ${envPath}. Running construct sync...`);
3882
3921
  setCheapestProviderPreference(envPath, true);
3883
3922
  await cmdSync([]);
3884
3923
  return;
3885
3924
  }
3886
- if (args.includes('--list') || args[0] === 'list') {
3925
+ if (args.includes('--list') || sub === 'list') {
3887
3926
  const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
3888
- const { listChatModels } = await import('../apps/chat/engine/models.mjs');
3889
3927
  const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
3890
- const models = listChatModels({ env: process.env, cwd: process.cwd() });
3928
+ const models = listAvailableModels({ env: process.env, cwd: process.cwd() });
3891
3929
  if (args.includes('--json')) {
3892
3930
  println(JSON.stringify({ catalog, models }, null, 2));
3893
3931
  return;
@@ -3899,8 +3937,7 @@ async function cmdModels(args) {
3899
3937
  }
3900
3938
  return;
3901
3939
  }
3902
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3903
- const result = readCurrentModels(envPath, registry.models ?? {});
3940
+ const result = readCurrentModels(envPath, {});
3904
3941
  const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
3905
3942
  println('Current model assignments:');
3906
3943
  for (const currentTier of ['reasoning', 'standard', 'fast']) {
@@ -3912,8 +3949,8 @@ async function cmdModels(args) {
3912
3949
  println('');
3913
3950
  println('No tier has a model selected. Construct ships with no default.');
3914
3951
  println('Pick one of:');
3915
- println(' • Dashboard Models page (single source of truth)');
3916
- println(' • construct models --set=<provider/model-id> --tier=reasoning|standard|fast');
3952
+ println(' • `construct models` (single source of truth)');
3953
+ println(' • construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
3917
3954
  println(' • construct models --apply Poll OpenRouter free catalog and seed all tiers');
3918
3955
  }
3919
3956
  }
@@ -4440,12 +4477,6 @@ async function cmdDocsSite(args = []) {
4440
4477
  }
4441
4478
  }
4442
4479
 
4443
- async function cmdDashboardSync(args) {
4444
- const { runDashboardStaticCli } = await import('../lib/dashboard-static.mjs');
4445
- const exitCode = await runDashboardStaticCli(args, { rootDir: ROOT_DIR });
4446
- if (exitCode) process.exit(exitCode);
4447
- }
4448
-
4449
4480
  async function cmdBootstrap(args) {
4450
4481
  const verbose = args.includes('--verbose') || args.includes('-v');
4451
4482
  const { runBootstrap } = await import('../lib/bootstrap.mjs');
@@ -4783,14 +4814,15 @@ async function cmdLintResearch() {
4783
4814
  }
4784
4815
 
4785
4816
  async function cmdLintAgents() {
4786
- const { validateRegistryFile } = await import('../lib/specialists/schema.mjs');
4787
- const registryPath = path.join(ROOT_DIR, 'specialists', 'registry.json');
4788
- const { errors, agentCount } = validateRegistryFile({ registryPath, rootDir: ROOT_DIR });
4817
+ const { validateRegistry } = await import('../lib/specialists/schema.mjs');
4818
+ const { loadRegistry } = await import('../lib/registry/loader.mjs');
4819
+ const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
4820
+ const { errors, agentCount } = validateRegistry(registry, { rootDir: ROOT_DIR });
4789
4821
  if (errors.length === 0) {
4790
- console.log(`specialists/registry.json: ${agentCount} agents, 0 errors`);
4822
+ console.log(`specialists/org: 0 errors`);
4791
4823
  return;
4792
4824
  }
4793
- console.error(`specialists/registry.json: ${errors.length} error(s) across ${agentCount} agents`);
4825
+ console.error(`specialists/org: ${errors.length} error(s)`);
4794
4826
  for (const err of errors) console.error(` ${err}`);
4795
4827
  process.exit(1);
4796
4828
  }
@@ -4799,10 +4831,10 @@ async function cmdLintContracts() {
4799
4831
  const { validateContractsFile } = await import('../lib/contracts/validate.mjs');
4800
4832
  const result = validateContractsFile({ repoRoot: ROOT_DIR });
4801
4833
  if (result.ok) {
4802
- console.log('specialists/contracts.json: 0 errors');
4834
+ console.log('specialists/unified-registry.json contracts: 0 errors');
4803
4835
  return;
4804
4836
  }
4805
- console.error(`specialists/contracts.json: ${result.errors.length} error(s)`);
4837
+ console.error(`specialists/unified-registry.json contracts: ${result.errors.length} error(s)`);
4806
4838
  for (const err of result.errors) console.error(` ${err}`);
4807
4839
  process.exit(1);
4808
4840
  }
@@ -4830,7 +4862,7 @@ async function cmdSpecialist(args) {
4830
4862
  const { createSpecialistDraft } = await import('../lib/specialists/scaffold.mjs');
4831
4863
  try {
4832
4864
  const { relPath } = createSpecialistDraft({ rootDir: ROOT_DIR, role });
4833
- println(`Created ${relPath} — fill in the stubbed sections, add it to specialists/registry.json, then run \`construct specialist lint\`.`);
4865
+ println(`Created ${relPath} — fill in the stubbed sections, add it to specialists/unified-registry.json, then run \`construct specialist lint\`.`);
4834
4866
  } catch (e) { errorln(e.message); process.exit(1); }
4835
4867
  return;
4836
4868
  }
@@ -5136,7 +5168,7 @@ async function cmdCreds(args) {
5136
5168
  for (const p of providers) {
5137
5169
  println(` ${p.configured ? '✓' : '·'} ${p.id}`);
5138
5170
  }
5139
- println('\n Set keys as values or op:// refs in ~/.construct/config.env; run `construct creds login copilot` for GitHub Copilot.');
5171
+ println(`\n Set keys as values or op:// refs in ${getUserEnvPath(HOME)}; run \`construct creds login copilot\` for GitHub Copilot.`);
5140
5172
  } catch { /* readiness view is informational */ }
5141
5173
  return;
5142
5174
  }
@@ -5144,6 +5176,13 @@ async function cmdCreds(args) {
5144
5176
  if (sub === 'test') {
5145
5177
  const provider = args[1];
5146
5178
  if (!provider) { errorln('Usage: construct creds test <provider>'); process.exit(1); }
5179
+ if (['openrouter', 'anthropic', 'openai'].includes(provider)) {
5180
+ const { probeProviderConnection } = await import('../lib/providers/connection-probe.mjs');
5181
+ const result = await probeProviderConnection(provider, { env: process.env, cwd: process.cwd() });
5182
+ println(`${provider}: ${result.ok ? 'ok' : 'unhealthy'}`);
5183
+ if (result.code) println(` ${result.code}${result.status ? ` (${result.status})` : ''}`);
5184
+ process.exit(result.ok ? 0 : 1);
5185
+ }
5147
5186
  const { resolveProviders } = await import('../lib/providers/registry.mjs');
5148
5187
  const { providers } = await resolveProviders({ rootDir: ROOT_DIR, env: process.env });
5149
5188
  if (!providers[provider]) { errorln(`Unknown provider: ${provider}`); process.exit(1); }
@@ -5521,7 +5560,6 @@ async function readStdin() {
5521
5560
  const handlers = new Map([
5522
5561
  // Core
5523
5562
  ['dev', cmdDev],
5524
- ['dashboard', cmdDashboard],
5525
5563
  ['stop', cmdStop],
5526
5564
  ['status', cmdStatus],
5527
5565
  ['install', cmdInstall],
@@ -5553,6 +5591,12 @@ const handlers = new Map([
5553
5591
  ['registry:status', cmdRegistryStatus],
5554
5592
  ['registry:validate', cmdRegistryValidate],
5555
5593
  ['registry:generate-docs', cmdRegistryGenerateDocs],
5594
+ ['registry:diff', cmdRegistryDiff],
5595
+ ['registry:prune', cmdRegistryPrune],
5596
+ ['team:add', cmdTeamAdd],
5597
+ ['team:remove', cmdTeamRemove],
5598
+ ['specialist:add', cmdSpecialistAdd],
5599
+ ['specialist:remove', cmdSpecialistRemove],
5556
5600
  ['rules', cmdRulesUsage],
5557
5601
  // Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
5558
5602
  // catalog, and per-turn accounting still run, but no CLI surface exposes
@@ -5594,10 +5638,6 @@ const handlers = new Map([
5594
5638
  const { runAcpServer } = await import('../lib/acp/server.mjs');
5595
5639
  runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
5596
5640
  }],
5597
- ['chat', async (args) => {
5598
- const { runChat } = await import('../lib/chat/cli.mjs');
5599
- process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
5600
- }],
5601
5641
  ['beads:stats', async (args) => {
5602
5642
  const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
5603
5643
  const jsonOutput = args.includes('--json');
@@ -5646,14 +5686,8 @@ const handlers = new Map([
5646
5686
  return;
5647
5687
  }
5648
5688
  if (sub === 'show') {
5649
- const { readFileSync, existsSync } = await import('node:fs');
5650
- const { join } = await import('node:path');
5651
- const inventoryPath = join(ROOT_DIR, 'specialists', 'policy-inventory.json');
5652
- if (!existsSync(inventoryPath)) {
5653
- errorln('specialists/policy-inventory.json not found');
5654
- process.exit(1);
5655
- }
5656
- const { policies } = JSON.parse(readFileSync(inventoryPath, 'utf8'));
5689
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
5690
+ const policies = Object.values(registry.policies ?? {});
5657
5691
  if (args.includes('--json')) {
5658
5692
  println(JSON.stringify(policies, null, 2));
5659
5693
  return;
@@ -5722,7 +5756,6 @@ const handlers = new Map([
5722
5756
  ['docs:update', cmdDocsUpdate],
5723
5757
  ['docs:check', cmdDocsCheck],
5724
5758
  ['docs:site', cmdDocsSite],
5725
- ['dashboard:sync', cmdDashboardSync],
5726
5759
  ['lint:comments', cmdLintComments],
5727
5760
  ['lint:templates', cmdLintTemplates],
5728
5761
  ['gates:audit', cmdGatesAudit],
@@ -5812,7 +5845,12 @@ const handlers = new Map([
5812
5845
  const sub = args[0];
5813
5846
  if (sub === 'review') return runTeamReviewCli(args.slice(1));
5814
5847
  if (sub === 'templates') { listTeamTemplates(); return; }
5815
- errorln(`Unknown team subcommand: ${sub}. Available: review, templates`);
5848
+ if (sub === 'list' || sub === 'show') {
5849
+ const { runTeamList, runTeamShow } = await import('../lib/registry/cli.mjs');
5850
+ if (sub === 'list') return runTeamList(args.slice(1), { rootDir: ROOT_DIR, println, errorln });
5851
+ return runTeamShow(args.slice(1), { rootDir: ROOT_DIR, println, errorln });
5852
+ }
5853
+ errorln(`Unknown team subcommand: ${sub}. Available: list, show, review, templates`);
5816
5854
  process.exit(1);
5817
5855
  }],
5818
5856
  ['role', async (args) => {
@@ -6198,6 +6236,22 @@ const handlers = new Map([
6198
6236
  }],
6199
6237
  ['skills', async (args) => {
6200
6238
  const sub = args[0];
6239
+ if (sub === 'suggest') {
6240
+ const { suggestSkills } = await import('../lib/skills/router.mjs');
6241
+ const query = args.slice(1).join(' ').trim();
6242
+ if (!query) {
6243
+ errorln('Usage: construct skills suggest <intent>');
6244
+ process.exit(1);
6245
+ }
6246
+ const result = suggestSkills({ intent: query, rootDir: ROOT_DIR });
6247
+ println(JSON.stringify(result, null, 2));
6248
+ return;
6249
+ }
6250
+ if (sub === 'routing') {
6251
+ const p = path.join(ROOT_DIR, 'skills', 'routing.json');
6252
+ println(fs.readFileSync(p, 'utf8'));
6253
+ return;
6254
+ }
6201
6255
  if (sub === 'scope' || !sub) {
6202
6256
  const { runSkillsScopeCli } = await import('../lib/skills-scope.mjs');
6203
6257
  return runSkillsScopeCli(args.slice(sub === 'scope' ? 1 : 0));
@@ -6209,7 +6263,7 @@ const handlers = new Map([
6209
6263
  if (sub === 'usage' || sub === 'orphans' || sub === 'hot' || sub === 'correlate-quality' || sub === 'backfill-postgres') {
6210
6264
  return cmdSkillsUsage(sub, args.slice(1));
6211
6265
  }
6212
- errorln(`Unknown skills subcommand: ${sub}. Available: scope, apply, usage, orphans, hot, correlate-quality, backfill-postgres`);
6266
+ errorln(`Unknown skills subcommand: ${sub}. Available: scope, apply, suggest, routing, usage, orphans, hot, correlate-quality, backfill-postgres`);
6213
6267
  process.exit(1);
6214
6268
  }],
6215
6269
  ['ask', async (args) => {
@@ -6378,18 +6432,18 @@ const handlers = new Map([
6378
6432
  errorln(`Unknown sandbox subcommand: ${sub}. Available: create, list, delete, prune`);
6379
6433
  process.exit(1);
6380
6434
  }],
6381
- ['profile', async (args) => {
6435
+ ['scope', async (args) => {
6382
6436
  const sub = args[0] || 'show';
6383
- const { listProfiles, loadProfile, resolveActiveProfile } = await import('../lib/profiles/loader.mjs');
6437
+ const { listScopes, loadScope, resolveActiveScope } = await import('../lib/scopes/loader.mjs');
6384
6438
  if (sub === 'list') {
6385
- for (const id of listProfiles()) {
6386
- const p = loadProfile(id);
6439
+ for (const id of listScopes()) {
6440
+ const p = loadScope(id);
6387
6441
  process.stdout.write(` ${id.padEnd(14)} ${p?.displayName || ''}\n`);
6388
6442
  }
6389
6443
  return;
6390
6444
  }
6391
6445
  if (sub === 'show') {
6392
- const active = resolveActiveProfile(process.cwd());
6446
+ const active = resolveActiveScope(process.cwd());
6393
6447
  process.stdout.write(JSON.stringify({
6394
6448
  id: active.id,
6395
6449
  displayName: active.displayName,
@@ -6402,12 +6456,12 @@ const handlers = new Map([
6402
6456
  if (sub === 'set') {
6403
6457
  const id = args[1];
6404
6458
  if (!id) {
6405
- errorln('Usage: construct profile set <id> [--yes] [--dry-run]');
6459
+ errorln('Usage: construct scope set <id> [--yes] [--dry-run]');
6406
6460
  process.exit(1);
6407
6461
  }
6408
- const target = loadProfile(id);
6462
+ const target = loadScope(id);
6409
6463
  if (!target) {
6410
- errorln(`Unknown profile: ${id}. Available: ${listProfiles().join(', ')}`);
6464
+ errorln(`Unknown scope: ${id}. Available: ${listScopes().join(', ')}`);
6411
6465
  process.exit(1);
6412
6466
  }
6413
6467
  const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
@@ -6425,12 +6479,12 @@ const handlers = new Map([
6425
6479
  const cfg = loaded?.raw ? structuredClone(loaded.raw) : { version: 1 };
6426
6480
  if (!cfg.version) cfg.version = 1;
6427
6481
 
6428
- const currentId = cfg.profile || 'rnd';
6482
+ const currentId = cfg.scope || 'rnd';
6429
6483
  if (currentId === id) {
6430
- println(`Profile already set to ${id} (${target.displayName}). Nothing to do.`);
6484
+ println(`Scope already set to ${id} (${target.displayName}). Nothing to do.`);
6431
6485
  return;
6432
6486
  }
6433
- const current = loadProfile(currentId) || { id: currentId, displayName: currentId, roles: [], intake: { types: [], stages: [] }, departments: [] };
6487
+ const current = loadScope(currentId) || { id: currentId, displayName: currentId, roles: [], intake: { types: [], stages: [] }, departments: [] };
6434
6488
 
6435
6489
  // Render a structural diff so the operator sees what loop they are switching into.
6436
6490
  const setDiff = (label, before, after) => {
@@ -6446,7 +6500,7 @@ const handlers = new Map([
6446
6500
  };
6447
6501
 
6448
6502
  println('');
6449
- println(`About to switch active profile:`);
6503
+ println(`About to switch active scope:`);
6450
6504
  println(` from: ${currentId} (${current.displayName})`);
6451
6505
  println(` to: ${id} (${target.displayName})`);
6452
6506
  println('');
@@ -6459,10 +6513,10 @@ const handlers = new Map([
6459
6513
  setDiff('departments', cDept, tDept);
6460
6514
  println('');
6461
6515
  println(`Files that will change:`);
6462
- println(` ${pathMod.relative(process.cwd(), cfgPath)} (profile field: ${currentId} -> ${id})`);
6516
+ println(` ${pathMod.relative(process.cwd(), cfgPath)} (scope field: ${currentId} -> ${id})`);
6463
6517
  println('');
6464
- println(`The change only writes the profile field. No specialists are added or removed.`);
6465
- println(`Sync runs against the new profile on next \`construct sync\`.`);
6518
+ println(`The change only writes the scope field. No specialists are added or removed.`);
6519
+ println(`Sync runs against the new scope on next \`construct sync\`.`);
6466
6520
  println('');
6467
6521
 
6468
6522
  if (dryRun) {
@@ -6472,7 +6526,7 @@ const handlers = new Map([
6472
6526
  if (!skipConfirm && isTTY) {
6473
6527
  const readline = await import('node:readline');
6474
6528
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6475
- const answer = await new Promise((res) => rl.question(`Switch profile to ${id}? (Y/n) `, (a) => res(a.trim().toLowerCase())));
6529
+ const answer = await new Promise((res) => rl.question(`Switch scope to ${id}? (Y/n) `, (a) => res(a.trim().toLowerCase())));
6476
6530
  rl.close();
6477
6531
  if (answer && answer !== 'y' && answer !== 'yes') {
6478
6532
  println('Aborted. No config was changed.');
@@ -6480,15 +6534,15 @@ const handlers = new Map([
6480
6534
  }
6481
6535
  }
6482
6536
 
6483
- cfg.profile = id;
6537
+ cfg.scope = id;
6484
6538
  writeProjectConfig(cfgPath, cfg, { validate: true });
6485
- process.stdout.write(`✓ profile set to ${id} (${target.displayName})\n`);
6539
+ process.stdout.write(`✓ scope set to ${id} (${target.displayName})\n`);
6486
6540
  return;
6487
6541
  }
6488
6542
  if (sub === 'create') {
6489
6543
  const id = args[1];
6490
6544
  if (!id) {
6491
- errorln('Usage: construct profile create <id> [--display="..."] [--role=<id>...] [--department=<id>:<displayName>...] [--yes]');
6545
+ errorln('Usage: construct scope create <id> [--display="..."] [--role=<id>...] [--department=<id>:<displayName>...] [--yes]');
6492
6546
  errorln(' Run with no flags for interactive mode; pass --yes to skip the confirmation prompt.');
6493
6547
  process.exit(1);
6494
6548
  }
@@ -6526,8 +6580,8 @@ const handlers = new Map([
6526
6580
  const ask = (q, def = '') => new Promise((res) => rl.question(`${q}${def ? ` [${def}]` : ''} `, (a) => res(a.trim() || def)));
6527
6581
 
6528
6582
  println('');
6529
- println(`Scaffolding a new profile draft. Three questions, then a preview.`);
6530
- println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/profile-lifecycle.md`);
6583
+ println(`Scaffolding a new scope draft. Three questions, then a preview.`);
6584
+ println(`Background: docs/guides/concepts/persona-research.md and docs/guides/concepts/scope-lifecycle.md`);
6531
6585
  println('');
6532
6586
  if (!displayName) displayName = await ask('Display name?', id);
6533
6587
  if (seedDepartments.length === 0) {
@@ -6546,23 +6600,23 @@ const handlers = new Map([
6546
6600
  if (!displayName) displayName = id;
6547
6601
 
6548
6602
  const pathMod = await import('node:path');
6549
- const draftRoot = pathMod.join(process.cwd(), '.cx', 'profiles', `draft-${id}`);
6603
+ const draftRoot = pathMod.join(process.cwd(), '.cx', 'scopes', `draft-${id}`);
6550
6604
 
6551
6605
  println('');
6552
- println(`About to scaffold a draft profile:`);
6606
+ println(`About to scaffold a draft scope:`);
6553
6607
  println(` id: ${id}`);
6554
6608
  println(` displayName: ${displayName}`);
6555
6609
  println(` departments: ${seedDepartments.length}${seedDepartments.length ? ` (${seedDepartments.map((d) => d.id).join(', ')})` : ''}`);
6556
6610
  println(` roles: ${seedRoles.length}${seedRoles.length ? ` (${seedRoles.join(', ')})` : ''}`);
6557
6611
  println('');
6558
6612
  println(`Files that will be written under ${pathMod.relative(process.cwd(), draftRoot)}/:`);
6559
- println(` profile.json structural manifest`);
6613
+ println(` scope.json structural manifest`);
6560
6614
  println(` requirements.md research brief naming each cx-* specialist`);
6561
6615
  for (const r of seedRoles) println(` personas/${r}.md`.padEnd(40) + ` persona artifact (filled during discover)`);
6562
6616
  for (const d of seedDepartments) println(` departments/${d.id}.md`.padEnd(40) + ` department charter (filled during frame)`);
6563
6617
  println('');
6564
- println(`Nothing else is touched. No config is changed, no active profile is switched.`);
6565
- println(`To activate this profile after promotion: construct profile set ${id}`);
6618
+ println(`Nothing else is touched. No config is changed, no active scope is switched.`);
6619
+ println(`To activate this scope after promotion: construct scope set ${id}`);
6566
6620
  println('');
6567
6621
 
6568
6622
  if (dryRun) {
@@ -6580,16 +6634,16 @@ const handlers = new Map([
6580
6634
  }
6581
6635
  }
6582
6636
 
6583
- const { createDraftProfile } = await import('../lib/profiles/lifecycle.mjs');
6637
+ const { createDraftScope } = await import('../lib/scopes/lifecycle.mjs');
6584
6638
  try {
6585
- const result = createDraftProfile({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
6639
+ const result = createDraftScope({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
6586
6640
  println('');
6587
6641
  println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
6588
6642
  println('');
6589
- println(`Next steps (in order, per docs/guides/concepts/profile-lifecycle.md):`);
6643
+ println(`Next steps (in order, per docs/guides/concepts/scope-lifecycle.md):`);
6590
6644
  println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
6591
6645
  println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
6592
- println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in profile.json.`);
6646
+ println(` 3. Architect (cx-architect): reconcile role reuse vs new; populate departments[] in scope.json.`);
6593
6647
  println(` 4. Validate (cx-evaluator): run persona-eval and classifier-eval against representative signals.`);
6594
6648
  println('');
6595
6649
  println(`Inspect the brief: open ${pathMod.relative(process.cwd(), result.briefPath)}`);
@@ -6600,11 +6654,11 @@ const handlers = new Map([
6600
6654
  }
6601
6655
  }
6602
6656
  if (sub === 'drafts') {
6603
- const { listDrafts } = await import('../lib/profiles/lifecycle.mjs');
6657
+ const { listDrafts } = await import('../lib/scopes/lifecycle.mjs');
6604
6658
  const drafts = listDrafts(process.cwd());
6605
6659
  if (drafts.length === 0) { process.stdout.write(' (no drafts)\n'); return; }
6606
6660
  for (const d of drafts) {
6607
- const flags = [d.hasBrief ? 'brief' : null, d.hasProfile ? 'profile' : null].filter(Boolean).join(', ');
6661
+ const flags = [d.hasBrief ? 'brief' : null, d.hasScope ? 'scope' : null].filter(Boolean).join(', ');
6608
6662
  process.stdout.write(` ${d.id.padEnd(20)} ${flags}\n`);
6609
6663
  }
6610
6664
  return;
@@ -6614,7 +6668,7 @@ const handlers = new Map([
6614
6668
  const reasonArg = args.find((a) => a.startsWith('--reason='));
6615
6669
  let reason = reasonArg ? reasonArg.split('=')[1] : '';
6616
6670
  if (!id) {
6617
- errorln('Usage: construct profile archive <id> --reason="..." [--yes] [--dry-run]');
6671
+ errorln('Usage: construct scope archive <id> --reason="..." [--yes] [--dry-run]');
6618
6672
  process.exit(1);
6619
6673
  }
6620
6674
  const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
@@ -6623,18 +6677,18 @@ const handlers = new Map([
6623
6677
 
6624
6678
  const pathMod = await import('node:path');
6625
6679
  const fsMod = await import('node:fs');
6626
- const target = loadProfile(id);
6680
+ const target = loadScope(id);
6627
6681
  if (!target) {
6628
- errorln(`Unknown profile: ${id}. Curated catalog: ${listProfiles().join(', ')}`);
6682
+ errorln(`Unknown scope: ${id}. Curated catalog: ${listScopes().join(', ')}`);
6629
6683
  process.exit(1);
6630
6684
  }
6631
6685
  const fileURL = await import('node:url');
6632
6686
  const moduleDir = pathMod.dirname(fileURL.fileURLToPath(import.meta.url));
6633
6687
  const repoRoot = pathMod.resolve(moduleDir, '..');
6634
- const srcProfile = pathMod.join(repoRoot, 'profiles', `${id}.json`);
6688
+ const srcScope = pathMod.join(repoRoot, 'specialists', 'org', 'scopes', `${id}.json`);
6635
6689
  const tableRel = typeof target?.intake?.classificationTable === 'string' ? target.intake.classificationTable : null;
6636
6690
  const srcTable = tableRel ? pathMod.join(repoRoot, tableRel) : null;
6637
- const dstDir = pathMod.join(repoRoot, 'archive', 'profiles', id);
6691
+ const dstDir = pathMod.join(repoRoot, 'archive', 'scopes', id);
6638
6692
 
6639
6693
  // Gather reason interactively if missing. The lifecycle module already
6640
6694
  // enforces a >= 8 char reason, but the operator should see why before
@@ -6651,13 +6705,13 @@ const handlers = new Map([
6651
6705
  }
6652
6706
 
6653
6707
  println('');
6654
- println(`About to archive curated profile:`);
6708
+ println(`About to archive curated scope:`);
6655
6709
  println(` id: ${id}`);
6656
6710
  println(` displayName: ${target.displayName || id}`);
6657
6711
  println(` reason: ${reason}`);
6658
6712
  println('');
6659
6713
  println(`Files that will move:`);
6660
- println(` ${pathMod.relative(process.cwd(), srcProfile)}`);
6714
+ println(` ${pathMod.relative(process.cwd(), srcScope)}`);
6661
6715
  println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, `${id}.json`))}`);
6662
6716
  if (srcTable && fsMod.existsSync(srcTable)) {
6663
6717
  println(` ${pathMod.relative(process.cwd(), srcTable)}`);
@@ -6668,7 +6722,7 @@ const handlers = new Map([
6668
6722
  println(`What stays:`);
6669
6723
  println(` Observations under .cx/observations/ are not touched.`);
6670
6724
  println(` Outcomes under .cx/outcomes/ are not touched.`);
6671
- println(` Any project still configured with profile=${id} will fall back to rnd on next load.`);
6725
+ println(` Any project still configured with scope=${id} will fall back to rnd on next load.`);
6672
6726
  println('');
6673
6727
 
6674
6728
  if (dryRun) {
@@ -6686,9 +6740,9 @@ const handlers = new Map([
6686
6740
  }
6687
6741
  }
6688
6742
 
6689
- const { archiveProfile } = await import('../lib/profiles/lifecycle.mjs');
6743
+ const { archiveScope } = await import('../lib/scopes/lifecycle.mjs');
6690
6744
  try {
6691
- const { archived } = archiveProfile({ id, reason });
6745
+ const { archived } = archiveScope({ id, reason });
6692
6746
  process.stdout.write(`✓ archived ${id} to ${pathMod.relative(process.cwd(), archived)}\n`);
6693
6747
  return;
6694
6748
  } catch (err) {
@@ -6698,25 +6752,19 @@ const handlers = new Map([
6698
6752
  }
6699
6753
  if (sub === 'health') {
6700
6754
  const id = args[1];
6701
- if (!id) { errorln('Usage: construct profile health <id> [--days=N]'); process.exit(1); }
6755
+ if (!id) { errorln('Usage: construct scope health <id> [--days=N]'); process.exit(1); }
6702
6756
  const daysArg = args.find((a) => a.startsWith('--days='));
6703
6757
  const windowDays = daysArg ? Number(daysArg.split('=')[1]) || 30 : 30;
6704
- const { profileHealth } = await import('../lib/profiles/lifecycle.mjs');
6705
- const report = profileHealth(process.cwd(), id, { windowDays });
6758
+ const { scopeHealth } = await import('../lib/scopes/lifecycle.mjs');
6759
+ const report = scopeHealth(process.cwd(), id, { windowDays });
6706
6760
  process.stdout.write(JSON.stringify(report, null, 2) + '\n');
6707
6761
  return;
6708
6762
  }
6709
- errorln(`Unknown profile subcommand: ${sub}. Available: list, show, set, create, drafts, archive, health`);
6763
+ errorln(`Unknown scope subcommand: ${sub}. Available: list, show, set, create, drafts, archive, health`);
6710
6764
  process.exit(1);
6711
6765
  }],
6712
6766
  ]);
6713
6767
 
6714
- // No command provided → show interactive menu
6715
- if (!command) {
6716
- await showInteractiveMenu();
6717
- process.exit(0);
6718
- }
6719
-
6720
6768
  // Help flags → show usage
6721
6769
  if (command === '--help' || command === '-h' || command === 'help') {
6722
6770
  usage();
@@ -6737,6 +6785,19 @@ if (command === '--version' || command === '-V') {
6737
6785
  process.exit(0);
6738
6786
  }
6739
6787
 
6788
+ // Bare invocation stays non-interactive. OpenCode is the first-class
6789
+ // conversational surface; this CLI remains the setup/admin/headless substrate.
6790
+ if (!command) {
6791
+ usage();
6792
+ process.exit(0);
6793
+ }
6794
+
6795
+ if (command.startsWith('-')) {
6796
+ errorln(uiFail(`Unknown global flag: ${COLORS.bold}${command}${COLORS.reset}`, { colors: COLORS, glyphs: GLYPHS }));
6797
+ errorln(`${COLORS.dim}OpenCode is the primary conversation surface; run ${COLORS.reset}construct sync${COLORS.dim} to refresh adapters, or ${COLORS.reset}construct --help${COLORS.dim} for CLI commands.${COLORS.reset}`);
6798
+ process.exit(1);
6799
+ }
6800
+
6740
6801
  // Unknown-flag guidance for commands that fully declare their flags (strictFlags
6741
6802
  // in the spec). A stray or typo'd flag on these is a user error, not a no-op:
6742
6803
  // reject it with the nearest declared flag and the command's help, matching how
@@ -6800,7 +6861,12 @@ function validateKnownFlags(name, argv) {
6800
6861
 
6801
6862
  const handler = handlers.get(command);
6802
6863
  if (!handler) {
6803
- errorln(`Unknown command: ${command}`);
6864
+ if (command === 'chat') {
6865
+ errorln('Unknown command: chat');
6866
+ errorln(`${COLORS.dim}Construct's local conversation UI has been removed. Use OpenCode as the primary conversation surface after running ${COLORS.reset}construct sync${COLORS.dim}.${COLORS.reset}`);
6867
+ process.exit(1);
6868
+ }
6869
+ errorln(uiFail(`Unknown command: ${COLORS.bold}${command}${COLORS.reset}`, { colors: COLORS, glyphs: GLYPHS }));
6804
6870
  // Three-tier suggestion: prefix match → substring match → Levenshtein
6805
6871
  // distance ≤ 2. Catches single-letter typos, missing-letter typos, and
6806
6872
  // transpositions ('sycn' → 'sync') without dragging in a full fuzzy lib.
@@ -6831,9 +6897,9 @@ if (!handler) {
6831
6897
  if (d < bestDist) { bestDist = d; suggestion = name; }
6832
6898
  }
6833
6899
  }
6834
- if (suggestion) errorln(`Did you mean: construct ${suggestion}?`);
6900
+ if (suggestion) errorln(`${COLORS.muted}Did you mean${COLORS.reset} ${COLORS.highlight}construct ${suggestion}${COLORS.reset}?`);
6835
6901
  println('');
6836
- println(`${COLORS.dim}Run 'construct --help' for available commands${COLORS.reset}`);
6902
+ println(`${COLORS.dim}Run ${COLORS.reset}construct --help${COLORS.dim} for available commands${COLORS.reset}`);
6837
6903
  process.exit(1);
6838
6904
  }
6839
6905