@geraldmaron/construct 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (505) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +235 -209
  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 +55 -54
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +90 -24
  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/cli.mjs +12 -2
  44. package/lib/doctor/source-checkout.mjs +3 -4
  45. package/lib/doctor/watchers/consistency.mjs +148 -43
  46. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  47. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  48. package/lib/doctor/watchers/service-health.mjs +4 -32
  49. package/lib/document-export.mjs +73 -9
  50. package/lib/document-extract.mjs +10 -198
  51. package/lib/embed/artifact.mjs +2 -3
  52. package/lib/embed/cli.mjs +1 -1
  53. package/lib/embed/config.mjs +1 -2
  54. package/lib/embed/customer-profiles.mjs +1 -1
  55. package/lib/embed/daemon.mjs +5 -6
  56. package/lib/embed/demand-fetch.mjs +114 -2
  57. package/lib/embed/docs-lifecycle.mjs +9 -5
  58. package/lib/embed/intake-metrics.mjs +2 -2
  59. package/lib/embed/notifications.mjs +2 -3
  60. package/lib/embed/recommendation-store.mjs +25 -22
  61. package/lib/embed/role-framing.mjs +4 -9
  62. package/lib/embedded-contract/capability.mjs +4 -2
  63. package/lib/embedded-contract/contract-version.mjs +1 -1
  64. package/lib/env-config.mjs +2 -2
  65. package/lib/flavors/loader.mjs +21 -19
  66. package/lib/graph/build-from-registry.mjs +31 -7
  67. package/lib/graph/staleness.mjs +1 -1
  68. package/lib/headhunt.mjs +82 -35
  69. package/lib/hooks/agent-tracker.mjs +2 -2
  70. package/lib/hooks/config-protection.mjs +1 -1
  71. package/lib/hooks/registry-sync.mjs +3 -3
  72. package/lib/host-capabilities.mjs +5 -5
  73. package/lib/improvement/controller.mjs +2 -2
  74. package/lib/init-unified.mjs +19 -21
  75. package/lib/intake/classify.mjs +26 -0
  76. package/lib/intake/daemon.mjs +2 -3
  77. package/lib/intake/prepare.mjs +10 -3
  78. package/lib/intake/session-prelude.mjs +1 -1
  79. package/lib/intake/tables/creative.mjs +9 -9
  80. package/lib/intake/tables/operations.mjs +4 -4
  81. package/lib/intake/tables/research.mjs +2 -2
  82. package/lib/integrations/intake-integrations.mjs +9 -10
  83. package/lib/knowledge/research-store.mjs +2 -2
  84. package/lib/mcp/server.mjs +104 -61
  85. package/lib/mcp/tool-budget.mjs +1 -46
  86. package/lib/mcp/tool-recovery.mjs +44 -0
  87. package/lib/mcp/tools/artifact-author.mjs +36 -0
  88. package/lib/mcp/tools/find-tool.mjs +86 -0
  89. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  90. package/lib/mcp/tools/project.mjs +4 -3
  91. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  92. package/lib/mcp/tools/skills.mjs +39 -22
  93. package/lib/mcp/tools/telemetry.mjs +2 -1
  94. package/lib/mcp/tools/workflow.mjs +1 -1
  95. package/lib/mcp-platform-config.mjs +7 -5
  96. package/lib/migrations/index.mjs +4 -2
  97. package/lib/migrations/v2-unified-registry.mjs +35 -0
  98. package/lib/model-router.mjs +203 -36
  99. package/lib/models/catalog.mjs +25 -9
  100. package/lib/models/execution-capability-profile.mjs +3 -3
  101. package/lib/models/execution-policy.mjs +7 -6
  102. package/lib/models/provider-poll.mjs +18 -4
  103. package/lib/opencode-config.mjs +56 -1
  104. package/lib/opencode-runtime-plugin.mjs +13 -10
  105. package/lib/oracle/artifact-gate.mjs +14 -4
  106. package/lib/oracle/cli.mjs +2 -0
  107. package/lib/oracle/dispatch.mjs +18 -1
  108. package/lib/oracle/execute.mjs +39 -7
  109. package/lib/oracle/index.mjs +1 -1
  110. package/lib/oracle/org-graph.mjs +10 -5
  111. package/lib/oracle/policy.mjs +6 -0
  112. package/lib/oracle/read-model.mjs +103 -6
  113. package/lib/oracle/reconcile.mjs +31 -4
  114. package/lib/oracle/remediation-dispatch.mjs +53 -0
  115. package/lib/oracle/routing.mjs +5 -0
  116. package/lib/oracle/synthesize.mjs +80 -3
  117. package/lib/orchestration/routing-tables.mjs +44 -10
  118. package/lib/orchestration/runtime.mjs +2 -0
  119. package/lib/orchestration/worker.mjs +1 -1
  120. package/lib/orchestration-policy.mjs +351 -62
  121. package/lib/overrides/resolver.mjs +1 -12
  122. package/lib/parity.mjs +37 -11
  123. package/lib/policy/engine.mjs +80 -15
  124. package/lib/prompt-composer.js +51 -15
  125. package/lib/prompt-metadata.mjs +3 -2
  126. package/lib/providers/connection-probe.mjs +58 -0
  127. package/lib/providers/copilot-auth.mjs +1 -1
  128. package/lib/providers/credential-bootstrap.mjs +1 -1
  129. package/lib/providers/op-run.mjs +3 -4
  130. package/lib/providers/secret-resolver.mjs +31 -0
  131. package/lib/publish-tooling.mjs +3 -11
  132. package/lib/publish.mjs +12 -29
  133. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  134. package/lib/reflect.mjs +2 -2
  135. package/lib/registry/agent-manifest.mjs +190 -0
  136. package/lib/registry/assemble.mjs +133 -0
  137. package/lib/registry/catalog.mjs +171 -0
  138. package/lib/registry/cli.mjs +590 -6
  139. package/lib/registry/consolidation.mjs +5 -4
  140. package/lib/registry/docs-sync.mjs +67 -0
  141. package/lib/registry/loader.mjs +147 -0
  142. package/lib/registry/org-io.mjs +76 -0
  143. package/lib/registry/retired-paths.mjs +71 -0
  144. package/lib/registry/surface-map.mjs +5 -7
  145. package/lib/registry/validator.mjs +438 -0
  146. package/lib/research-execution-policy.mjs +173 -0
  147. package/lib/roles/catalog.mjs +4 -9
  148. package/lib/roles/fence.mjs +107 -1
  149. package/lib/roles/flavor-bindings.mjs +68 -0
  150. package/lib/roles/gateway.mjs +140 -29
  151. package/lib/roles/manifest.mjs +22 -13
  152. package/lib/roles/router.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 +6 -26
  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 -8
  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 +269 -51
  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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  433. package/apps/chat/engine/loop-driver.mjs +0 -211
  434. package/apps/chat/engine/models.mjs +0 -122
  435. package/apps/chat/engine/provider-adapters.mjs +0 -171
  436. package/apps/chat/engine/tools/permission.mjs +0 -54
  437. package/apps/chat/engine/tools/primitives.mjs +0 -180
  438. package/apps/chat/engine/tools/registry.mjs +0 -122
  439. package/apps/chat/engine/turn-controls.mjs +0 -70
  440. package/lib/boundary.mjs +0 -127
  441. package/lib/certification/dashboard-api.mjs +0 -71
  442. package/lib/chat/cli.mjs +0 -333
  443. package/lib/chat/command-suggest.mjs +0 -161
  444. package/lib/chat/commands.mjs +0 -215
  445. package/lib/chat/config.mjs +0 -142
  446. package/lib/chat/context-compactor.mjs +0 -250
  447. package/lib/chat/context-continuation.mjs +0 -253
  448. package/lib/chat/continuation-source.mjs +0 -58
  449. package/lib/chat/demo-guide.mjs +0 -61
  450. package/lib/chat/design-tokens.mjs +0 -91
  451. package/lib/chat/desktop-binary.mjs +0 -81
  452. package/lib/chat/desktop-build.mjs +0 -130
  453. package/lib/chat/desktop-launcher.mjs +0 -133
  454. package/lib/chat/evidence.mjs +0 -145
  455. package/lib/chat/export.mjs +0 -74
  456. package/lib/chat/harness/driver.mjs +0 -91
  457. package/lib/chat/list-picker.mjs +0 -112
  458. package/lib/chat/model-picker.mjs +0 -356
  459. package/lib/chat/openrouter-fallback.mjs +0 -151
  460. package/lib/chat/permission-prompt.mjs +0 -33
  461. package/lib/chat/picker-catalog.mjs +0 -45
  462. package/lib/chat/policy-telemetry.mjs +0 -34
  463. package/lib/chat/present.mjs +0 -246
  464. package/lib/chat/session-context.mjs +0 -39
  465. package/lib/chat/session-persist.mjs +0 -73
  466. package/lib/chat/session-restore.mjs +0 -71
  467. package/lib/chat/session-settings.mjs +0 -53
  468. package/lib/chat/system-prompt.mjs +0 -52
  469. package/lib/chat/transparency.mjs +0 -93
  470. package/lib/chat/tui/color-scheme.mjs +0 -42
  471. package/lib/chat/tui/markdown.mjs +0 -123
  472. package/lib/chat/tui/presentation.mjs +0 -100
  473. package/lib/chat/tui/render.mjs +0 -500
  474. package/lib/chat/tui/turn-block.mjs +0 -284
  475. package/lib/chat/tui/turn-present.mjs +0 -18
  476. package/lib/chat/tui/turn-state.mjs +0 -88
  477. package/lib/chat/tui/usage.mjs +0 -122
  478. package/lib/chat/web-commands.mjs +0 -146
  479. package/lib/chat/web-launcher.mjs +0 -63
  480. package/lib/chat/web-picker-keys.mjs +0 -46
  481. package/lib/chat/web-session.mjs +0 -159
  482. package/lib/config/alias.mjs +0 -57
  483. package/lib/dashboard-demo.mjs +0 -71
  484. package/lib/dashboard-static.mjs +0 -175
  485. package/lib/install/desktop-binary-download.mjs +0 -88
  486. package/lib/profiles/loader.mjs +0 -123
  487. package/lib/profiles/rebrand.mjs +0 -46
  488. package/lib/server/auth.mjs +0 -169
  489. package/lib/server/chat-loop.mjs +0 -622
  490. package/lib/server/chat.mjs +0 -336
  491. package/lib/server/cors.mjs +0 -77
  492. package/lib/server/csrf.mjs +0 -103
  493. package/lib/server/demo-preview.mjs +0 -63
  494. package/lib/server/index.mjs +0 -2641
  495. package/lib/server/insights.mjs +0 -780
  496. package/lib/server/langfuse-login.mjs +0 -58
  497. package/lib/server/rate-limit.mjs +0 -93
  498. package/lib/server/telemetry-login.mjs +0 -100
  499. package/lib/server/webhook.mjs +0 -512
  500. package/specialists/contracts.json +0 -1032
  501. package/specialists/contracts.schema.json +0 -83
  502. package/specialists/policy-inventory.json +0 -188
  503. package/specialists/registry.json +0 -1412
  504. package/specialists/role-manifests.json +0 -217
  505. 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
-
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
150
 
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' },
@@ -658,12 +672,17 @@ async function cmdDoctor() {
658
672
  // configured without a manual copy. Applied to process.env too so the check
659
673
  // below sees the just-migrated values in the same run.
660
674
  try {
661
- const { migrateLegacyModelConfig } = await import('../lib/config/legacy-config-migration.mjs');
675
+ const { migrateLegacyModelConfig, migrateLegacyCredentialConfig } = await import('../lib/config/legacy-config-migration.mjs');
662
676
  const migration = migrateLegacyModelConfig({ homeDir: HOME });
663
677
  if (migration.performed) {
664
678
  Object.assign(process.env, migration.migrated);
665
679
  println(` fix: migrated ${Object.keys(migration.migrated).join(', ')} from ${migration.legacyPath} → ${migration.xdgPath}`);
666
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
+ }
667
686
  } catch (err) {
668
687
  add(`Models — legacy config migration failed: ${err.message}`, false, true);
669
688
  }
@@ -680,7 +699,7 @@ async function cmdDoctor() {
680
699
  if (resolved.complete && !resolved.errors) {
681
700
  add('Models — all tiers configured', true);
682
701
  } else if (resolved.configured === 0) {
683
- 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);
684
703
  } else {
685
704
  const missing = Object.entries(resolved.models)
686
705
  .filter(([, v]) => !v)
@@ -862,19 +881,28 @@ async function cmdDoctor() {
862
881
 
863
882
  // Validate promptFile references and skill bindings
864
883
  try {
865
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
866
- const brokenPromptFiles = (registry.specialists ?? []).filter((s) => {
884
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
885
+ const brokenPromptFiles = Object.values(registry.specialists ?? {}).filter((s) => {
867
886
  if (!s.promptFile) return false;
868
887
  return !fs.existsSync(path.join(ROOT_DIR, s.promptFile));
869
888
  });
870
889
  add('All specialist promptFiles resolve', brokenPromptFiles.length === 0);
871
890
  const headhuntClassifierAgents = ['cx-docs-keeper', 'cx-researcher', 'cx-architect', 'cx-security', 'cx-engineer'];
872
- 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}`));
873
892
  const classifierOrphans = headhuntClassifierAgents.filter((n) => !registryNames.has(n));
874
893
  add('Headhunt classifier agents exist in registry', classifierOrphans.length === 0);
875
894
  const skillAudit = auditSkills({ rootDir: ROOT_DIR, silent: true });
876
895
  add('No declared skills missing on disk', skillAudit.pass, false);
877
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
+ );
878
906
  const specialistAudit = auditSpecialists({ rootDir: ROOT_DIR, silent: true });
879
907
  add('Specialist/skill audit cross-checks', specialistAudit.pass, false);
880
908
 
@@ -919,7 +947,8 @@ async function cmdDoctor() {
919
947
  if (fs.existsSync(beadsPreCommit) && !inCI) {
920
948
  const hp = spawnSync('git', ['config', '--get', 'core.hooksPath'], { cwd: ROOT_DIR, encoding: 'utf8' });
921
949
  const value = hp.status === 0 ? (hp.stdout || '').trim() : '';
922
- const wired = value === '.beads/hooks';
950
+ const absEquiv = path.join(ROOT_DIR, '.beads', 'hooks');
951
+ const wired = value === '.beads/hooks' || value === absEquiv;
923
952
  const label = wired
924
953
  ? 'Git hooks wired (core.hooksPath = .beads/hooks)'
925
954
  : value
@@ -1422,7 +1451,6 @@ async function cmdDev(args = []) {
1422
1451
  }
1423
1452
 
1424
1453
  println('');
1425
- println('Dashboard: http://127.0.0.1:4242');
1426
1454
  println('Run \x1b[32mconstruct\x1b[0m for commands');
1427
1455
  println('');
1428
1456
 
@@ -1449,29 +1477,6 @@ async function cmdStop() {
1449
1477
  } catch { /* best effort */ }
1450
1478
  }
1451
1479
 
1452
- async function cmdDashboard() {
1453
- if (restArgsCache.includes('--token')) {
1454
- const { generateToken, setDashboardToken, getDashboardToken } = await import('../lib/server/auth.mjs');
1455
- const existing = getDashboardToken();
1456
- if (existing) {
1457
- println(`Dashboard token already set. To rotate it, remove CONSTRUCT_DASHBOARD_TOKEN from ~/.construct/config.env and re-run.`);
1458
- return;
1459
- }
1460
- const token = generateToken();
1461
- setDashboardToken(token);
1462
- println(`Dashboard token generated and saved to ~/.construct/config.env`);
1463
- println(`CONSTRUCT_DASHBOARD_TOKEN=${token}`);
1464
- println(`Pass this token in the dashboard login prompt or as: Authorization: Bearer <token>`);
1465
- return;
1466
- }
1467
- const dashboard = await startDashboard({ rootDir: ROOT_DIR, homeDir: HOME });
1468
- if (dashboard.reused) {
1469
- println(`Construct dashboard already running on ${dashboard.url} (pid ${dashboard.pid})`);
1470
- } else {
1471
- println(`Started Construct dashboard on ${dashboard.url} (pid ${dashboard.pid})`);
1472
- }
1473
- }
1474
-
1475
1480
  async function cmdVersion() {
1476
1481
  const pkg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'package.json'), 'utf8'));
1477
1482
  println(`construct v${pkg.version}`);
@@ -1486,9 +1491,7 @@ async function cmdMigrate(args) {
1486
1491
  const candidates = pathArg
1487
1492
  ? [pathArg]
1488
1493
  : [
1489
- path.join(ROOT_DIR, 'specialists', 'registry.json'),
1490
- path.join(ROOT_DIR, 'specialists', 'contracts.json'),
1491
- path.join(ROOT_DIR, 'specialists', 'role-manifests.json'),
1494
+ path.join(ROOT_DIR, 'specialists', 'unified-registry.json'),
1492
1495
  path.join(process.cwd(), '.cx', 'config.json'),
1493
1496
  ];
1494
1497
 
@@ -1615,7 +1618,7 @@ async function cmdExport(args) {
1615
1618
  inputPath: resolvedIn,
1616
1619
  outputPath: resolvedOut,
1617
1620
  format: toFlag,
1618
- figures: toFlag === 'pdf' ? figures : false,
1621
+ figures,
1619
1622
  branding,
1620
1623
  repoRoot: ROOT_DIR,
1621
1624
  });
@@ -1635,19 +1638,17 @@ async function cmdPublish(args) {
1635
1638
  async function cmdTools(args) {
1636
1639
  const [sub, ...rest] = args;
1637
1640
  if (sub !== 'detect') {
1638
- 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]');
1639
1642
  process.exit(1);
1640
1643
  }
1641
1644
  const { detectPublishPipeline, formatToolsDetectReport } = await import('../lib/publish-tooling.mjs');
1642
1645
  const json = rest.includes('--json');
1643
1646
  const figures = !rest.includes('--no-figures');
1644
1647
  const demos = rest.filter((a) => a.startsWith('--demo=')).map((a) => a.slice(7));
1645
- const dashboardDemos = rest.filter((a) => a.startsWith('--dashboard-demo=')).map((a) => a.slice(17));
1646
1648
  const detection = detectPublishPipeline({
1647
1649
  format: 'pdf',
1648
1650
  includeFigures: figures,
1649
1651
  includeTerminalDemo: demos.length > 0,
1650
- includeDashboardDemo: dashboardDemos.length > 0,
1651
1652
  cwd: process.cwd(),
1652
1653
  repoRoot: ROOT_DIR,
1653
1654
  });
@@ -2106,7 +2107,7 @@ function printActiveConfig() {
2106
2107
  }
2107
2108
 
2108
2109
  async function printIntakeHelp() {
2109
- const { getRebrand } = await import('../lib/profiles/rebrand.mjs');
2110
+ const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
2110
2111
  const { intakeQueueLabel, signalNoun } = getRebrand(process.cwd());
2111
2112
  println(`Construct intake — inspect and process the ${intakeQueueLabel}.`);
2112
2113
  println('');
@@ -2224,7 +2225,7 @@ async function cmdIntake(args) {
2224
2225
  }
2225
2226
 
2226
2227
  const { createIntakeQueue } = await import('../lib/intake/queue.mjs');
2227
- const { getRebrand } = await import('../lib/profiles/rebrand.mjs');
2228
+ const { getRebrand } = await import('../lib/scopes/rebrand.mjs');
2228
2229
  const cwd = process.cwd();
2229
2230
  const rebrand = getRebrand(cwd);
2230
2231
  const queue = createIntakeQueue(cwd, process.env);
@@ -3652,7 +3653,7 @@ async function cmdUpgrade(args) {
3652
3653
  }
3653
3654
  }
3654
3655
  async function cmdValidate() {
3655
- const reg = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3656
+ const reg = loadRegistry({ rootDir: ROOT_DIR });
3656
3657
  const result = validateRegistry(reg, { rootDir: ROOT_DIR });
3657
3658
  if (result.valid) {
3658
3659
  println(`✓ Registry valid (${result.summary})`);
@@ -3695,9 +3696,9 @@ async function cmdOrchestrate(args) {
3695
3696
  // running in-process, proving the engine-as-service contract editors/CI use.
3696
3697
  if (rest.includes('--remote')) {
3697
3698
  const remoteVal = flag('--remote');
3698
- const base = (remoteVal && /^https?:\/\//.test(remoteVal)) ? remoteVal.replace(/\/$/, '') : `http://${process.env.BIND_HOST || '127.0.0.1'}:${process.env.PORT || '4242'}`;
3699
- const { getDashboardToken } = await import('../lib/server/auth.mjs');
3700
- 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;
3701
3702
  const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
3702
3703
  const positional = rest.filter((a) => !a.startsWith('--'));
3703
3704
  try {
@@ -3727,7 +3728,7 @@ async function cmdOrchestrate(args) {
3727
3728
  return;
3728
3729
  }
3729
3730
  } catch (err) {
3730
- 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.`);
3731
3732
  process.exit(1);
3732
3733
  }
3733
3734
  }
@@ -3839,10 +3840,9 @@ async function cmdModels(args) {
3839
3840
  if (sub === null) {
3840
3841
  errorln('Note: `construct models --tier=… --set=…` is a deprecated alias for `construct models set --tier=… --model=…`.');
3841
3842
  }
3842
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3843
3843
  const preferFree = args.includes('--prefer-free');
3844
3844
  const preferFreeSameFamily = args.includes('--prefer-free-same-family');
3845
- const inferred = setModelWithTierInference(envPath, tier, setModel, registry.models ?? {}, { preferFree, preferFreeSameFamily });
3845
+ const inferred = setModelWithTierInference(envPath, tier, setModel, {}, { preferFree, preferFreeSameFamily });
3846
3846
  println(`Set ${tier} -> ${setModel} in ${envPath}`);
3847
3847
  if (preferFreeSameFamily) println('Prefer-free-same-family mode: enabled');
3848
3848
  if (preferFree) println('Prefer-free mode: enabled');
@@ -3873,10 +3873,9 @@ async function cmdModels(args) {
3873
3873
  if (args.includes('--apply')) {
3874
3874
  const apiKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
3875
3875
  const models = await pollFreeModels(apiKey);
3876
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3877
3876
  const selections = {};
3878
3877
  for (const currentTier of ['reasoning', 'standard', 'fast']) {
3879
- const fallbacks = registry.models?.[currentTier]?.fallback ?? [];
3878
+ const fallbacks = [];
3880
3879
  const id = selectForTier(models, currentTier, fallbacks);
3881
3880
  if (id) selections[currentTier] = id;
3882
3881
  }
@@ -3925,9 +3924,8 @@ async function cmdModels(args) {
3925
3924
  }
3926
3925
  if (args.includes('--list') || sub === 'list') {
3927
3926
  const { getProviderModelCatalog } = await import('../lib/model-router.mjs');
3928
- const { listChatModels } = await import('../apps/chat/engine/models.mjs');
3929
3927
  const catalog = getProviderModelCatalog({ env: process.env, cwd: process.cwd() });
3930
- const models = listChatModels({ env: process.env, cwd: process.cwd() });
3928
+ const models = listAvailableModels({ env: process.env, cwd: process.cwd() });
3931
3929
  if (args.includes('--json')) {
3932
3930
  println(JSON.stringify({ catalog, models }, null, 2));
3933
3931
  return;
@@ -3939,8 +3937,7 @@ async function cmdModels(args) {
3939
3937
  }
3940
3938
  return;
3941
3939
  }
3942
- const registry = JSON.parse(fs.readFileSync(path.join(ROOT_DIR, 'specialists', 'registry.json'), 'utf8'));
3943
- const result = readCurrentModels(envPath, registry.models ?? {});
3940
+ const result = readCurrentModels(envPath, {});
3944
3941
  const nothingSelected = ['reasoning', 'standard', 'fast'].every((t) => !result[t]);
3945
3942
  println('Current model assignments:');
3946
3943
  for (const currentTier of ['reasoning', 'standard', 'fast']) {
@@ -3952,7 +3949,7 @@ async function cmdModels(args) {
3952
3949
  println('');
3953
3950
  println('No tier has a model selected. Construct ships with no default.');
3954
3951
  println('Pick one of:');
3955
- println(' • Dashboard Models page (single source of truth)');
3952
+ println(' • `construct models` (single source of truth)');
3956
3953
  println(' • construct models set --tier=<reasoning|standard|fast> --model=<provider/model-id>');
3957
3954
  println(' • construct models --apply Poll OpenRouter free catalog and seed all tiers');
3958
3955
  }
@@ -4480,12 +4477,6 @@ async function cmdDocsSite(args = []) {
4480
4477
  }
4481
4478
  }
4482
4479
 
4483
- async function cmdDashboardSync(args) {
4484
- const { runDashboardStaticCli } = await import('../lib/dashboard-static.mjs');
4485
- const exitCode = await runDashboardStaticCli(args, { rootDir: ROOT_DIR });
4486
- if (exitCode) process.exit(exitCode);
4487
- }
4488
-
4489
4480
  async function cmdBootstrap(args) {
4490
4481
  const verbose = args.includes('--verbose') || args.includes('-v');
4491
4482
  const { runBootstrap } = await import('../lib/bootstrap.mjs');
@@ -4823,14 +4814,15 @@ async function cmdLintResearch() {
4823
4814
  }
4824
4815
 
4825
4816
  async function cmdLintAgents() {
4826
- const { validateRegistryFile } = await import('../lib/specialists/schema.mjs');
4827
- const registryPath = path.join(ROOT_DIR, 'specialists', 'registry.json');
4828
- 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 });
4829
4821
  if (errors.length === 0) {
4830
- console.log(`specialists/registry.json: ${agentCount} agents, 0 errors`);
4822
+ console.log(`specialists/org: 0 errors`);
4831
4823
  return;
4832
4824
  }
4833
- console.error(`specialists/registry.json: ${errors.length} error(s) across ${agentCount} agents`);
4825
+ console.error(`specialists/org: ${errors.length} error(s)`);
4834
4826
  for (const err of errors) console.error(` ${err}`);
4835
4827
  process.exit(1);
4836
4828
  }
@@ -4839,10 +4831,10 @@ async function cmdLintContracts() {
4839
4831
  const { validateContractsFile } = await import('../lib/contracts/validate.mjs');
4840
4832
  const result = validateContractsFile({ repoRoot: ROOT_DIR });
4841
4833
  if (result.ok) {
4842
- console.log('specialists/contracts.json: 0 errors');
4834
+ console.log('specialists/unified-registry.json contracts: 0 errors');
4843
4835
  return;
4844
4836
  }
4845
- console.error(`specialists/contracts.json: ${result.errors.length} error(s)`);
4837
+ console.error(`specialists/unified-registry.json contracts: ${result.errors.length} error(s)`);
4846
4838
  for (const err of result.errors) console.error(` ${err}`);
4847
4839
  process.exit(1);
4848
4840
  }
@@ -4870,7 +4862,7 @@ async function cmdSpecialist(args) {
4870
4862
  const { createSpecialistDraft } = await import('../lib/specialists/scaffold.mjs');
4871
4863
  try {
4872
4864
  const { relPath } = createSpecialistDraft({ rootDir: ROOT_DIR, role });
4873
- 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\`.`);
4874
4866
  } catch (e) { errorln(e.message); process.exit(1); }
4875
4867
  return;
4876
4868
  }
@@ -5176,7 +5168,7 @@ async function cmdCreds(args) {
5176
5168
  for (const p of providers) {
5177
5169
  println(` ${p.configured ? '✓' : '·'} ${p.id}`);
5178
5170
  }
5179
- 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.`);
5180
5172
  } catch { /* readiness view is informational */ }
5181
5173
  return;
5182
5174
  }
@@ -5184,6 +5176,13 @@ async function cmdCreds(args) {
5184
5176
  if (sub === 'test') {
5185
5177
  const provider = args[1];
5186
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
+ }
5187
5186
  const { resolveProviders } = await import('../lib/providers/registry.mjs');
5188
5187
  const { providers } = await resolveProviders({ rootDir: ROOT_DIR, env: process.env });
5189
5188
  if (!providers[provider]) { errorln(`Unknown provider: ${provider}`); process.exit(1); }
@@ -5561,7 +5560,6 @@ async function readStdin() {
5561
5560
  const handlers = new Map([
5562
5561
  // Core
5563
5562
  ['dev', cmdDev],
5564
- ['dashboard', cmdDashboard],
5565
5563
  ['stop', cmdStop],
5566
5564
  ['status', cmdStatus],
5567
5565
  ['install', cmdInstall],
@@ -5593,6 +5591,12 @@ const handlers = new Map([
5593
5591
  ['registry:status', cmdRegistryStatus],
5594
5592
  ['registry:validate', cmdRegistryValidate],
5595
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],
5596
5600
  ['rules', cmdRulesUsage],
5597
5601
  // Pricing / cost readouts are stubbed out: the ledger writes, model-pricing
5598
5602
  // catalog, and per-turn accounting still run, but no CLI surface exposes
@@ -5634,10 +5638,6 @@ const handlers = new Map([
5634
5638
  const { runAcpServer } = await import('../lib/acp/server.mjs');
5635
5639
  runAcpServer({ input: process.stdin, output: process.stdout, env: process.env, defaultCwd: process.cwd() });
5636
5640
  }],
5637
- ['chat', async (args) => {
5638
- const { runChat } = await import('../lib/chat/cli.mjs');
5639
- process.exitCode = await runChat(args, { env: process.env, cwd: process.cwd() });
5640
- }],
5641
5641
  ['beads:stats', async (args) => {
5642
5642
  const { getContentionStats, getHumanStatus } = await import('../lib/beads-optimistic.mjs');
5643
5643
  const jsonOutput = args.includes('--json');
@@ -5686,14 +5686,8 @@ const handlers = new Map([
5686
5686
  return;
5687
5687
  }
5688
5688
  if (sub === 'show') {
5689
- const { readFileSync, existsSync } = await import('node:fs');
5690
- const { join } = await import('node:path');
5691
- const inventoryPath = join(ROOT_DIR, 'specialists', 'policy-inventory.json');
5692
- if (!existsSync(inventoryPath)) {
5693
- errorln('specialists/policy-inventory.json not found');
5694
- process.exit(1);
5695
- }
5696
- const { policies } = JSON.parse(readFileSync(inventoryPath, 'utf8'));
5689
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
5690
+ const policies = Object.values(registry.policies ?? {});
5697
5691
  if (args.includes('--json')) {
5698
5692
  println(JSON.stringify(policies, null, 2));
5699
5693
  return;
@@ -5762,7 +5756,6 @@ const handlers = new Map([
5762
5756
  ['docs:update', cmdDocsUpdate],
5763
5757
  ['docs:check', cmdDocsCheck],
5764
5758
  ['docs:site', cmdDocsSite],
5765
- ['dashboard:sync', cmdDashboardSync],
5766
5759
  ['lint:comments', cmdLintComments],
5767
5760
  ['lint:templates', cmdLintTemplates],
5768
5761
  ['gates:audit', cmdGatesAudit],
@@ -5852,7 +5845,12 @@ const handlers = new Map([
5852
5845
  const sub = args[0];
5853
5846
  if (sub === 'review') return runTeamReviewCli(args.slice(1));
5854
5847
  if (sub === 'templates') { listTeamTemplates(); return; }
5855
- 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`);
5856
5854
  process.exit(1);
5857
5855
  }],
5858
5856
  ['role', async (args) => {
@@ -6238,6 +6236,22 @@ const handlers = new Map([
6238
6236
  }],
6239
6237
  ['skills', async (args) => {
6240
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
+ }
6241
6255
  if (sub === 'scope' || !sub) {
6242
6256
  const { runSkillsScopeCli } = await import('../lib/skills-scope.mjs');
6243
6257
  return runSkillsScopeCli(args.slice(sub === 'scope' ? 1 : 0));
@@ -6249,7 +6263,7 @@ const handlers = new Map([
6249
6263
  if (sub === 'usage' || sub === 'orphans' || sub === 'hot' || sub === 'correlate-quality' || sub === 'backfill-postgres') {
6250
6264
  return cmdSkillsUsage(sub, args.slice(1));
6251
6265
  }
6252
- 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`);
6253
6267
  process.exit(1);
6254
6268
  }],
6255
6269
  ['ask', async (args) => {
@@ -6418,18 +6432,18 @@ const handlers = new Map([
6418
6432
  errorln(`Unknown sandbox subcommand: ${sub}. Available: create, list, delete, prune`);
6419
6433
  process.exit(1);
6420
6434
  }],
6421
- ['profile', async (args) => {
6435
+ ['scope', async (args) => {
6422
6436
  const sub = args[0] || 'show';
6423
- const { listProfiles, loadProfile, resolveActiveProfile } = await import('../lib/profiles/loader.mjs');
6437
+ const { listScopes, loadScope, resolveActiveScope } = await import('../lib/scopes/loader.mjs');
6424
6438
  if (sub === 'list') {
6425
- for (const id of listProfiles()) {
6426
- const p = loadProfile(id);
6439
+ for (const id of listScopes()) {
6440
+ const p = loadScope(id);
6427
6441
  process.stdout.write(` ${id.padEnd(14)} ${p?.displayName || ''}\n`);
6428
6442
  }
6429
6443
  return;
6430
6444
  }
6431
6445
  if (sub === 'show') {
6432
- const active = resolveActiveProfile(process.cwd());
6446
+ const active = resolveActiveScope(process.cwd());
6433
6447
  process.stdout.write(JSON.stringify({
6434
6448
  id: active.id,
6435
6449
  displayName: active.displayName,
@@ -6442,12 +6456,12 @@ const handlers = new Map([
6442
6456
  if (sub === 'set') {
6443
6457
  const id = args[1];
6444
6458
  if (!id) {
6445
- errorln('Usage: construct profile set <id> [--yes] [--dry-run]');
6459
+ errorln('Usage: construct scope set <id> [--yes] [--dry-run]');
6446
6460
  process.exit(1);
6447
6461
  }
6448
- const target = loadProfile(id);
6462
+ const target = loadScope(id);
6449
6463
  if (!target) {
6450
- errorln(`Unknown profile: ${id}. Available: ${listProfiles().join(', ')}`);
6464
+ errorln(`Unknown scope: ${id}. Available: ${listScopes().join(', ')}`);
6451
6465
  process.exit(1);
6452
6466
  }
6453
6467
  const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
@@ -6465,12 +6479,12 @@ const handlers = new Map([
6465
6479
  const cfg = loaded?.raw ? structuredClone(loaded.raw) : { version: 1 };
6466
6480
  if (!cfg.version) cfg.version = 1;
6467
6481
 
6468
- const currentId = cfg.profile || 'rnd';
6482
+ const currentId = cfg.scope || 'rnd';
6469
6483
  if (currentId === id) {
6470
- println(`Profile already set to ${id} (${target.displayName}). Nothing to do.`);
6484
+ println(`Scope already set to ${id} (${target.displayName}). Nothing to do.`);
6471
6485
  return;
6472
6486
  }
6473
- 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: [] };
6474
6488
 
6475
6489
  // Render a structural diff so the operator sees what loop they are switching into.
6476
6490
  const setDiff = (label, before, after) => {
@@ -6486,7 +6500,7 @@ const handlers = new Map([
6486
6500
  };
6487
6501
 
6488
6502
  println('');
6489
- println(`About to switch active profile:`);
6503
+ println(`About to switch active scope:`);
6490
6504
  println(` from: ${currentId} (${current.displayName})`);
6491
6505
  println(` to: ${id} (${target.displayName})`);
6492
6506
  println('');
@@ -6499,10 +6513,10 @@ const handlers = new Map([
6499
6513
  setDiff('departments', cDept, tDept);
6500
6514
  println('');
6501
6515
  println(`Files that will change:`);
6502
- println(` ${pathMod.relative(process.cwd(), cfgPath)} (profile field: ${currentId} -> ${id})`);
6516
+ println(` ${pathMod.relative(process.cwd(), cfgPath)} (scope field: ${currentId} -> ${id})`);
6503
6517
  println('');
6504
- println(`The change only writes the profile field. No specialists are added or removed.`);
6505
- 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\`.`);
6506
6520
  println('');
6507
6521
 
6508
6522
  if (dryRun) {
@@ -6512,7 +6526,7 @@ const handlers = new Map([
6512
6526
  if (!skipConfirm && isTTY) {
6513
6527
  const readline = await import('node:readline');
6514
6528
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
6515
- 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())));
6516
6530
  rl.close();
6517
6531
  if (answer && answer !== 'y' && answer !== 'yes') {
6518
6532
  println('Aborted. No config was changed.');
@@ -6520,15 +6534,15 @@ const handlers = new Map([
6520
6534
  }
6521
6535
  }
6522
6536
 
6523
- cfg.profile = id;
6537
+ cfg.scope = id;
6524
6538
  writeProjectConfig(cfgPath, cfg, { validate: true });
6525
- process.stdout.write(`✓ profile set to ${id} (${target.displayName})\n`);
6539
+ process.stdout.write(`✓ scope set to ${id} (${target.displayName})\n`);
6526
6540
  return;
6527
6541
  }
6528
6542
  if (sub === 'create') {
6529
6543
  const id = args[1];
6530
6544
  if (!id) {
6531
- 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]');
6532
6546
  errorln(' Run with no flags for interactive mode; pass --yes to skip the confirmation prompt.');
6533
6547
  process.exit(1);
6534
6548
  }
@@ -6566,8 +6580,8 @@ const handlers = new Map([
6566
6580
  const ask = (q, def = '') => new Promise((res) => rl.question(`${q}${def ? ` [${def}]` : ''} `, (a) => res(a.trim() || def)));
6567
6581
 
6568
6582
  println('');
6569
- println(`Scaffolding a new profile draft. Three questions, then a preview.`);
6570
- 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`);
6571
6585
  println('');
6572
6586
  if (!displayName) displayName = await ask('Display name?', id);
6573
6587
  if (seedDepartments.length === 0) {
@@ -6586,23 +6600,23 @@ const handlers = new Map([
6586
6600
  if (!displayName) displayName = id;
6587
6601
 
6588
6602
  const pathMod = await import('node:path');
6589
- const draftRoot = pathMod.join(process.cwd(), '.cx', 'profiles', `draft-${id}`);
6603
+ const draftRoot = pathMod.join(process.cwd(), '.cx', 'scopes', `draft-${id}`);
6590
6604
 
6591
6605
  println('');
6592
- println(`About to scaffold a draft profile:`);
6606
+ println(`About to scaffold a draft scope:`);
6593
6607
  println(` id: ${id}`);
6594
6608
  println(` displayName: ${displayName}`);
6595
6609
  println(` departments: ${seedDepartments.length}${seedDepartments.length ? ` (${seedDepartments.map((d) => d.id).join(', ')})` : ''}`);
6596
6610
  println(` roles: ${seedRoles.length}${seedRoles.length ? ` (${seedRoles.join(', ')})` : ''}`);
6597
6611
  println('');
6598
6612
  println(`Files that will be written under ${pathMod.relative(process.cwd(), draftRoot)}/:`);
6599
- println(` profile.json structural manifest`);
6613
+ println(` scope.json structural manifest`);
6600
6614
  println(` requirements.md research brief naming each cx-* specialist`);
6601
6615
  for (const r of seedRoles) println(` personas/${r}.md`.padEnd(40) + ` persona artifact (filled during discover)`);
6602
6616
  for (const d of seedDepartments) println(` departments/${d.id}.md`.padEnd(40) + ` department charter (filled during frame)`);
6603
6617
  println('');
6604
- println(`Nothing else is touched. No config is changed, no active profile is switched.`);
6605
- 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}`);
6606
6620
  println('');
6607
6621
 
6608
6622
  if (dryRun) {
@@ -6620,16 +6634,16 @@ const handlers = new Map([
6620
6634
  }
6621
6635
  }
6622
6636
 
6623
- const { createDraftProfile } = await import('../lib/profiles/lifecycle.mjs');
6637
+ const { createDraftScope } = await import('../lib/scopes/lifecycle.mjs');
6624
6638
  try {
6625
- const result = createDraftProfile({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
6639
+ const result = createDraftScope({ cwd: process.cwd(), id, displayName, seedRoles, seedDepartments });
6626
6640
  println('');
6627
6641
  println(`✓ draft created at ${pathMod.relative(process.cwd(), result.dir)}`);
6628
6642
  println('');
6629
- 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):`);
6630
6644
  println(` 1. Discover (cx-ux-researcher): fill personas/<role>.md from interviews + primary sources.`);
6631
6645
  println(` 2. Frame (cx-product-manager): fill departments/<dept>.md charters and intake taxonomy.`);
6632
- 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.`);
6633
6647
  println(` 4. Validate (cx-evaluator): run persona-eval and classifier-eval against representative signals.`);
6634
6648
  println('');
6635
6649
  println(`Inspect the brief: open ${pathMod.relative(process.cwd(), result.briefPath)}`);
@@ -6640,11 +6654,11 @@ const handlers = new Map([
6640
6654
  }
6641
6655
  }
6642
6656
  if (sub === 'drafts') {
6643
- const { listDrafts } = await import('../lib/profiles/lifecycle.mjs');
6657
+ const { listDrafts } = await import('../lib/scopes/lifecycle.mjs');
6644
6658
  const drafts = listDrafts(process.cwd());
6645
6659
  if (drafts.length === 0) { process.stdout.write(' (no drafts)\n'); return; }
6646
6660
  for (const d of drafts) {
6647
- 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(', ');
6648
6662
  process.stdout.write(` ${d.id.padEnd(20)} ${flags}\n`);
6649
6663
  }
6650
6664
  return;
@@ -6654,7 +6668,7 @@ const handlers = new Map([
6654
6668
  const reasonArg = args.find((a) => a.startsWith('--reason='));
6655
6669
  let reason = reasonArg ? reasonArg.split('=')[1] : '';
6656
6670
  if (!id) {
6657
- errorln('Usage: construct profile archive <id> --reason="..." [--yes] [--dry-run]');
6671
+ errorln('Usage: construct scope archive <id> --reason="..." [--yes] [--dry-run]');
6658
6672
  process.exit(1);
6659
6673
  }
6660
6674
  const skipConfirm = args.includes('--yes') || args.includes('-y') || args.includes('--non-interactive');
@@ -6663,18 +6677,18 @@ const handlers = new Map([
6663
6677
 
6664
6678
  const pathMod = await import('node:path');
6665
6679
  const fsMod = await import('node:fs');
6666
- const target = loadProfile(id);
6680
+ const target = loadScope(id);
6667
6681
  if (!target) {
6668
- errorln(`Unknown profile: ${id}. Curated catalog: ${listProfiles().join(', ')}`);
6682
+ errorln(`Unknown scope: ${id}. Curated catalog: ${listScopes().join(', ')}`);
6669
6683
  process.exit(1);
6670
6684
  }
6671
6685
  const fileURL = await import('node:url');
6672
6686
  const moduleDir = pathMod.dirname(fileURL.fileURLToPath(import.meta.url));
6673
6687
  const repoRoot = pathMod.resolve(moduleDir, '..');
6674
- const srcProfile = pathMod.join(repoRoot, 'profiles', `${id}.json`);
6688
+ const srcScope = pathMod.join(repoRoot, 'specialists', 'org', 'scopes', `${id}.json`);
6675
6689
  const tableRel = typeof target?.intake?.classificationTable === 'string' ? target.intake.classificationTable : null;
6676
6690
  const srcTable = tableRel ? pathMod.join(repoRoot, tableRel) : null;
6677
- const dstDir = pathMod.join(repoRoot, 'archive', 'profiles', id);
6691
+ const dstDir = pathMod.join(repoRoot, 'archive', 'scopes', id);
6678
6692
 
6679
6693
  // Gather reason interactively if missing. The lifecycle module already
6680
6694
  // enforces a >= 8 char reason, but the operator should see why before
@@ -6691,13 +6705,13 @@ const handlers = new Map([
6691
6705
  }
6692
6706
 
6693
6707
  println('');
6694
- println(`About to archive curated profile:`);
6708
+ println(`About to archive curated scope:`);
6695
6709
  println(` id: ${id}`);
6696
6710
  println(` displayName: ${target.displayName || id}`);
6697
6711
  println(` reason: ${reason}`);
6698
6712
  println('');
6699
6713
  println(`Files that will move:`);
6700
- println(` ${pathMod.relative(process.cwd(), srcProfile)}`);
6714
+ println(` ${pathMod.relative(process.cwd(), srcScope)}`);
6701
6715
  println(` -> ${pathMod.relative(process.cwd(), pathMod.join(dstDir, `${id}.json`))}`);
6702
6716
  if (srcTable && fsMod.existsSync(srcTable)) {
6703
6717
  println(` ${pathMod.relative(process.cwd(), srcTable)}`);
@@ -6708,7 +6722,7 @@ const handlers = new Map([
6708
6722
  println(`What stays:`);
6709
6723
  println(` Observations under .cx/observations/ are not touched.`);
6710
6724
  println(` Outcomes under .cx/outcomes/ are not touched.`);
6711
- 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.`);
6712
6726
  println('');
6713
6727
 
6714
6728
  if (dryRun) {
@@ -6726,9 +6740,9 @@ const handlers = new Map([
6726
6740
  }
6727
6741
  }
6728
6742
 
6729
- const { archiveProfile } = await import('../lib/profiles/lifecycle.mjs');
6743
+ const { archiveScope } = await import('../lib/scopes/lifecycle.mjs');
6730
6744
  try {
6731
- const { archived } = archiveProfile({ id, reason });
6745
+ const { archived } = archiveScope({ id, reason });
6732
6746
  process.stdout.write(`✓ archived ${id} to ${pathMod.relative(process.cwd(), archived)}\n`);
6733
6747
  return;
6734
6748
  } catch (err) {
@@ -6738,25 +6752,19 @@ const handlers = new Map([
6738
6752
  }
6739
6753
  if (sub === 'health') {
6740
6754
  const id = args[1];
6741
- 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); }
6742
6756
  const daysArg = args.find((a) => a.startsWith('--days='));
6743
6757
  const windowDays = daysArg ? Number(daysArg.split('=')[1]) || 30 : 30;
6744
- const { profileHealth } = await import('../lib/profiles/lifecycle.mjs');
6745
- const report = profileHealth(process.cwd(), id, { windowDays });
6758
+ const { scopeHealth } = await import('../lib/scopes/lifecycle.mjs');
6759
+ const report = scopeHealth(process.cwd(), id, { windowDays });
6746
6760
  process.stdout.write(JSON.stringify(report, null, 2) + '\n');
6747
6761
  return;
6748
6762
  }
6749
- 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`);
6750
6764
  process.exit(1);
6751
6765
  }],
6752
6766
  ]);
6753
6767
 
6754
- // No command provided → show interactive menu
6755
- if (!command) {
6756
- await showInteractiveMenu();
6757
- process.exit(0);
6758
- }
6759
-
6760
6768
  // Help flags → show usage
6761
6769
  if (command === '--help' || command === '-h' || command === 'help') {
6762
6770
  usage();
@@ -6777,6 +6785,19 @@ if (command === '--version' || command === '-V') {
6777
6785
  process.exit(0);
6778
6786
  }
6779
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
+
6780
6801
  // Unknown-flag guidance for commands that fully declare their flags (strictFlags
6781
6802
  // in the spec). A stray or typo'd flag on these is a user error, not a no-op:
6782
6803
  // reject it with the nearest declared flag and the command's help, matching how
@@ -6840,7 +6861,12 @@ function validateKnownFlags(name, argv) {
6840
6861
 
6841
6862
  const handler = handlers.get(command);
6842
6863
  if (!handler) {
6843
- 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 }));
6844
6870
  // Three-tier suggestion: prefix match → substring match → Levenshtein
6845
6871
  // distance ≤ 2. Catches single-letter typos, missing-letter typos, and
6846
6872
  // transpositions ('sycn' → 'sync') without dragging in a full fuzzy lib.
@@ -6871,9 +6897,9 @@ if (!handler) {
6871
6897
  if (d < bestDist) { bestDist = d; suggestion = name; }
6872
6898
  }
6873
6899
  }
6874
- 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}?`);
6875
6901
  println('');
6876
- 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}`);
6877
6903
  process.exit(1);
6878
6904
  }
6879
6905