@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
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/service-manager.mjs — start, stop, and describe Construct runtime services.
3
3
  *
4
- * Manages the dashboard process, memory server (cm), and OpenCode.
4
+ * Manages the memory server (cm), OpenCode, and other runtime services.
5
5
  * startServices() is the single entry point for `construct dev` —
6
6
  * spawns whatever is available and returns a result per service.
7
7
  */
@@ -23,18 +23,12 @@ import { heartbeatPath } from './oracle/index.mjs';
23
23
  import { stateDir } from './config/xdg.mjs';
24
24
  import { doctorRoot } from './config/xdg.mjs';
25
25
 
26
- const DASHBOARD_STATE_FILE = 'dashboard.json';
27
-
28
26
  const INSTALL_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
29
27
 
30
28
  function runtimeStateDir(homeDir = os.homedir()) {
31
29
  return path.join(stateDir(homeDir), 'runtime');
32
30
  }
33
31
 
34
- function dashboardStatePath(homeDir = os.homedir()) {
35
- return path.join(runtimeStateDir(homeDir), DASHBOARD_STATE_FILE);
36
- }
37
-
38
32
  function readJson(filePath) {
39
33
  if (!fs.existsSync(filePath)) return null;
40
34
  try {
@@ -149,10 +143,6 @@ async function probeRuntimeHttp(url, {
149
143
  }
150
144
  }
151
145
 
152
- async function isDashboardRunning(port) {
153
- return probeRuntimeHttp(`http://127.0.0.1:${port}/`, { timeoutMs: 1000 });
154
- }
155
-
156
146
  async function isMemoryRunning(port) {
157
147
  const mcpReady = await probeRuntimeHttp(`http://127.0.0.1:${port}/`, {
158
148
  method: 'POST',
@@ -187,35 +177,6 @@ function spawnDetached(command, args, homeDir, logFile, options = {}) {
187
177
  return { child, logPath };
188
178
  }
189
179
 
190
- export function readDashboardState(homeDir = os.homedir()) {
191
- const state = readJson(dashboardStatePath(homeDir));
192
- if (!state) return null;
193
- if (!processExists(state.pid)) return null;
194
- return state;
195
- }
196
-
197
- export function clearDashboardState(homeDir = os.homedir()) {
198
- const filePath = dashboardStatePath(homeDir);
199
- if (fs.existsSync(filePath)) fs.rmSync(filePath, { force: true });
200
- }
201
-
202
- export function stopDashboard(homeDir = os.homedir()) {
203
- const state = readJson(dashboardStatePath(homeDir));
204
- if (!state?.pid) {
205
- clearDashboardState(homeDir);
206
- return { stopped: false, reason: 'not-running' };
207
- }
208
-
209
- if (!processExists(state.pid)) {
210
- clearDashboardState(homeDir);
211
- return { stopped: false, reason: 'stale-state' };
212
- }
213
-
214
- process.kill(Number(state.pid), 'SIGTERM');
215
- clearDashboardState(homeDir);
216
- return { stopped: true, pid: Number(state.pid), port: state.port };
217
- }
218
-
219
180
  function doctorStatePath(homeDir) {
220
181
  return path.join(stateDir(homeDir), 'doctor.json');
221
182
  }
@@ -273,67 +234,11 @@ export function startOracle({ rootDir, homeDir = os.homedir(), projectDir = root
273
234
  return { started: true, logPath };
274
235
  }
275
236
 
276
- export async function startDashboard({ rootDir, homeDir = os.homedir(), preferredPort = 4242 } = {}) {
277
- const existing = readDashboardState(homeDir);
278
- if (existing && await isDashboardRunning(existing.port)) {
279
- return { started: false, reused: true, pid: existing.pid, port: existing.port, url: existing.url };
280
- }
281
-
282
- if (existing) clearDashboardState(homeDir);
283
-
284
- const port = await findAvailablePort(preferredPort);
285
- const outPath = path.join(runtimeStateDir(homeDir), 'dashboard.log');
286
- fs.mkdirSync(path.dirname(outPath), { recursive: true });
287
- const out = fs.openSync(outPath, 'a');
288
- const err = fs.openSync(outPath, 'a');
289
-
290
- // Opt-in: with CONSTRUCT_OP_ENV_FILE set, launch the daemon under `op run` so
291
- // 1Password op:// refs resolve once at startup into its env. The biometric
292
- // unlock prompt makes first launch slower, so widen the readiness window then.
293
- const { command, args, wrapped } = wrapWithOpRun(
294
- process.execPath,
295
- [path.join(INSTALL_ROOT, 'lib', 'server', 'index.mjs')],
296
- { env: process.env },
297
- );
298
- const child = spawn(command, args, {
299
- detached: true,
300
- stdio: ['ignore', out, err],
301
- env: { ...process.env, PORT: String(port) },
302
- });
303
- child.unref();
304
-
305
- let ready = false;
306
- const maxTries = wrapped ? 150 : 30;
307
- for (let i = 0; i < maxTries; i += 1) {
308
- if (await probeRuntimePort(port)) { ready = true; break; }
309
- await new Promise((resolve) => setTimeout(resolve, 200));
310
- }
311
-
312
- if (!ready) {
313
- let logTail = '';
314
- try { logTail = fs.readFileSync(outPath, 'utf8').split('\n').slice(-12).join('\n'); } catch { /* log unreadable */ }
315
- const window = wrapped ? '30s (op run awaits 1Password unlock)' : '6s';
316
- return { started: false, failed: true, port, logPath: outPath, error: `dashboard did not bind 127.0.0.1:${port} within ${window}`, logTail };
317
- }
318
-
319
- const state = {
320
- pid: child.pid,
321
- port,
322
- url: `http://127.0.0.1:${port}`,
323
- startedAt: new Date().toISOString(),
324
- logPath: outPath,
325
- };
326
- writeJson(dashboardStatePath(homeDir), state);
327
- return { started: true, reused: false, ...state };
328
- }
329
-
330
237
  export async function getRuntimePorts(homeDir = os.homedir(), {
331
- dashboardProbeFn = isDashboardRunning,
332
238
  memoryProbeFn = isMemoryRunning,
333
239
  openCodeProbeFn = isBridgeRunning,
334
240
  findAvailablePortFn = findAvailablePort,
335
241
  } = {}) {
336
- const dashboard = readDashboardState(homeDir);
337
242
  const existing = parseEnvFile(getUserEnvPath(homeDir));
338
243
 
339
244
  async function resolvePort(key, fallback, probe = async (port) => probeRuntimePort(port)) {
@@ -345,7 +250,6 @@ export async function getRuntimePorts(homeDir = os.homedir(), {
345
250
  }
346
251
 
347
252
  return {
348
- dashboard: dashboard?.port ?? await resolvePort('DASHBOARD_PORT', 4242, dashboardProbeFn),
349
253
  memory: await resolvePort('MEMORY_PORT', derivedMemoryPort(), memoryProbeFn),
350
254
  bridge: await resolvePort('BRIDGE_PORT', 5173, openCodeProbeFn),
351
255
  copilotBridge: await resolvePort('COPILOT_BRIDGE_PORT', 5174, probeRuntimePort),
@@ -370,7 +274,6 @@ export async function describeRuntimeSupport() {
370
274
  }
371
275
 
372
276
  export const SELECTABLE_SERVICES = Object.freeze([
373
- { key: 'dashboard', label: 'Dashboard', description: 'Local operations dashboard on http://127.0.0.1:4242.' },
374
277
  { key: 'telemetry', label: 'Telemetry', description: 'Trace export / local JSONL traces.' },
375
278
  { key: 'memory', label: 'Memory (cm)', description: 'Persistent memory service (cm).' },
376
279
  { key: 'opencode', label: 'OpenCode', description: 'OpenCode bridge server.' },
@@ -393,7 +296,6 @@ export async function startServices({
393
296
  selected = null,
394
297
  describeRuntimeSupportFn = describeRuntimeSupport,
395
298
  getRuntimePortsFn = getRuntimePorts,
396
- startDashboardFn = startDashboard,
397
299
  loadConstructEnvFn = loadConstructEnv,
398
300
  spawnDetachedFn = spawnDetached,
399
301
  memoryProbeFn = isMemoryRunning,
@@ -407,7 +309,6 @@ export async function startServices({
407
309
  const wants = (key) => selected === null || selected.has(key);
408
310
 
409
311
  writeEnvValues(envPath, {
410
- DASHBOARD_PORT: String(ports.dashboard),
411
312
  MEMORY_PORT: String(ports.memory),
412
313
  BRIDGE_PORT: String(ports.bridge),
413
314
  COPILOT_BRIDGE_PORT: String(ports.copilotBridge),
@@ -429,14 +330,6 @@ export async function startServices({
429
330
  });
430
331
  }
431
332
 
432
- // Dashboard
433
- if (wants('dashboard')) {
434
- const dashboard = await startDashboardFn({ rootDir, homeDir, preferredPort: ports.dashboard });
435
- results.push(dashboard.failed
436
- ? { name: 'Dashboard', status: 'failed', note: `${dashboard.error} — see ${dashboard.logPath}` }
437
- : { name: 'Dashboard', url: dashboard.url, status: dashboard.reused ? 'reused' : 'started' });
438
- }
439
-
440
333
  const telemetryUrl = liveEnv.CONSTRUCT_TELEMETRY_URL ?? '';
441
334
  const traceBackend = resolveTraceBackend(liveEnv);
442
335
  if (wants('telemetry')) {
@@ -590,15 +483,6 @@ export async function stopServices({
590
483
  results.push({ name: 'Doctor', status: 'cleaned', note: 'stale state file removed' });
591
484
  }
592
485
 
593
- const dashboard = stopDashboard(homeDir);
594
- if (dashboard.stopped) {
595
- results.push({ name: 'Dashboard', status: 'stopped', note: `pid ${dashboard.pid}, port ${dashboard.port}` });
596
- } else if (dashboard.reason === 'stale-state') {
597
- results.push({ name: 'Dashboard', status: 'cleaned', note: 'stale state file removed (process already gone)' });
598
- } else {
599
- results.push({ name: 'Dashboard', status: 'not-running' });
600
- }
601
-
602
486
  const envPath = getUserEnvPath(homeDir);
603
487
  const envValues = parseEnvFile(envPath);
604
488
  const memoryPort = Number(envValues.MEMORY_PORT) || derivedMemoryPort();
@@ -13,7 +13,7 @@
13
13
  * 4. non-TTY → false (CI / scripts — caller can use --yes to opt in)
14
14
  * 5. interactive → prompt [Y/n], cache the answer
15
15
  *
16
- * Consent is stored as BOOTSTRAP_<NAME>=yes|no in ~/.construct/config.env
16
+ * Consent is stored as BOOTSTRAP_<NAME>=yes|no in ~/.config/construct/config.env
17
17
  * so subsequent runs of `construct init` don't re-prompt for the same
18
18
  * services. Pass `force = true` to skip the cache and re-prompt.
19
19
  */
package/lib/setup.mjs CHANGED
@@ -22,7 +22,7 @@ import path from 'node:path';
22
22
  import { spawn, spawnSync } from 'node:child_process';
23
23
 
24
24
  import { ensureUserConfigDir, getUserEnvPath, writeEnvValues } from './env-config.mjs';
25
- import { migrateLegacyModelConfig } from './config/legacy-config-migration.mjs';
25
+ import { migrateLegacyModelConfig, migrateLegacyCredentialConfig } from './config/legacy-config-migration.mjs';
26
26
  import { configDir, stateDir, doctorRoot } from './config/xdg.mjs';
27
27
  import { DEFAULT_WORKSPACE_PATH, WORKSPACE_DOCS_LANES } from './embed/config.mjs';
28
28
  import {
@@ -35,8 +35,6 @@ import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embedding
35
35
  import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
36
36
  import { consentToInstall } from './setup-prompts.mjs';
37
37
  import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
38
- import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
39
- import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
40
38
 
41
39
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
42
40
  const HOME = os.homedir();
@@ -355,7 +353,6 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupL
355
353
  console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
356
354
  console.log('\nWould check / install runtime tooling:');
357
355
  console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
358
- console.log(` · construct-chat desktop binary (from GitHub releases → ${path.join(stateDir(homeDir), 'bin')})`);
359
356
  console.log('\nWould reach the network / external services:');
360
357
  console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
361
358
  if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
@@ -417,6 +414,11 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
417
414
  console.log(`Migrated ${Object.keys(modelMigration.migrated).join(', ')} from ${modelMigration.legacyPath} → ${modelMigration.xdgPath}`);
418
415
  }
419
416
 
417
+ const credentialMigration = migrateLegacyCredentialConfig({ homeDir });
418
+ if (credentialMigration.performed) {
419
+ console.log(`Migrated credentials ${Object.keys(credentialMigration.migrated).join(', ')} from ${credentialMigration.legacyPath} → ${credentialMigration.xdgPath}`);
420
+ }
421
+
420
422
  if (cleanupLegacyGlobal) {
421
423
  runLegacyGlobalCleanup({ homeDir, dryRun: false });
422
424
  if (scope === 'project') {
@@ -466,28 +468,6 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
466
468
  if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
467
469
  }
468
470
 
469
- try {
470
- if (hasDesktopSource(ROOT_DIR)) {
471
- const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
472
- if (built.ok) {
473
- console.log(`Desktop binary: built from source → ${built.binaryPath}`);
474
- } else {
475
- console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
476
- }
477
- } else {
478
- const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
479
- if (desktop.status === 'downloaded') {
480
- console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
481
- } else if (desktop.status === 'failed') {
482
- console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
483
- } else {
484
- console.log(`Desktop binary: ${desktop.reason}`);
485
- }
486
- }
487
- } catch (err) {
488
- console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
489
- }
490
-
491
471
  const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
492
472
  ? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
493
473
  : { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
@@ -0,0 +1,98 @@
1
+ /**
2
+ * lib/skills/composition-graph.mjs — Skill routing and workflow composition audit.
3
+ */
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+
7
+ import { loadRegistry } from '../registry/loader.mjs';
8
+ import { listScopes } from '../scopes/loader.mjs';
9
+
10
+ const GET_SKILL_RE = /get_skill\("([^"]+)"\)/g;
11
+
12
+ function walkSkills(skillsDir, prefix = '') {
13
+ const out = new Set();
14
+ if (!fs.existsSync(skillsDir)) return out;
15
+ for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
16
+ const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
17
+ const full = path.join(skillsDir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ for (const s of walkSkills(full, rel)) out.add(s);
20
+ } else if (entry.name.endsWith('.md') && entry.name !== 'routing.md') {
21
+ out.add(rel.replace(/\.md$/, ''));
22
+ }
23
+ }
24
+ return out;
25
+ }
26
+
27
+ function collectRegistrySkills(registry) {
28
+ const set = new Set();
29
+ for (const agent of Object.values(registry.specialists || {})) {
30
+ for (const s of agent.skills || []) set.add(s);
31
+ }
32
+ return set;
33
+ }
34
+
35
+ function collectRoutingTargets(root) {
36
+ const set = new Set();
37
+ const jsonPath = path.join(root, 'skills', 'routing.json');
38
+ if (fs.existsSync(jsonPath)) {
39
+ try {
40
+ const data = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
41
+ for (const route of data.routes || data || []) {
42
+ const target = route.path || route.skill || route.skillId || route.target;
43
+ if (target) set.add(target.replace(/^skills\//, ''));
44
+ }
45
+ } catch { /* skip */ }
46
+ }
47
+ return set;
48
+ }
49
+
50
+ function collectWorkflowRefs(root) {
51
+ const set = new Set();
52
+ const docsDir = path.join(root, 'skills', 'docs');
53
+ if (!fs.existsSync(docsDir)) return set;
54
+ for (const f of fs.readdirSync(docsDir)) {
55
+ if (!f.endsWith('-workflow.md')) continue;
56
+ const body = fs.readFileSync(path.join(docsDir, f), 'utf8');
57
+ for (const m of body.matchAll(GET_SKILL_RE)) set.add(m[1]);
58
+ }
59
+ return set;
60
+ }
61
+
62
+ export function auditSkillComposition({ rootDir } = {}) {
63
+ const root = rootDir || path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..');
64
+ const allOnDisk = walkSkills(path.join(root, 'skills'));
65
+ const registry = loadRegistry({ rootDir: root });
66
+ const entitled = collectRegistrySkills(registry);
67
+ const routed = collectRoutingTargets(root);
68
+ const workflowRefs = collectWorkflowRefs(root);
69
+
70
+ const reachable = new Set([...entitled, ...routed, ...workflowRefs]);
71
+ for (const id of entitled) {
72
+ if (id.startsWith('roles/')) reachable.add(id);
73
+ }
74
+
75
+ const unreachable = [...allOnDisk].filter((id) => {
76
+ if (id.startsWith('roles/')) return false;
77
+ if (id.startsWith('brand/')) return false;
78
+ return !reachable.has(id);
79
+ });
80
+
81
+ const blocking = [];
82
+ for (const id of unreachable) {
83
+ if (id.startsWith('docs/') && id.endsWith('-workflow')) continue;
84
+ blocking.push(`unreachable skill: ${id} (not in registry, routing.json, or workflow chain)`);
85
+ }
86
+
87
+ return {
88
+ pass: blocking.length === 0,
89
+ blocking,
90
+ stats: {
91
+ onDisk: allOnDisk.size,
92
+ entitled: entitled.size,
93
+ routed: routed.size,
94
+ workflowRefs: workflowRefs.size,
95
+ unreachable: unreachable.length,
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * lib/skills/router.mjs — keyword + entitlement skill suggestion for agents.
3
+ */
4
+
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
8
+ import { loadRegistry } from '../registry/loader.mjs';
9
+ import { loadArtifactManifest } from '../artifact-manifest.mjs';
10
+ import { resolveActiveScope } from '../scopes/loader.mjs';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const ROOT = path.resolve(__dirname, '..', '..');
14
+
15
+ let _routes = null;
16
+
17
+ function loadRoutes(rootDir = ROOT) {
18
+ if (_routes) return _routes;
19
+ const p = path.join(rootDir, 'skills', 'routing.json');
20
+ _routes = JSON.parse(fs.readFileSync(p, 'utf8')).routes || [];
21
+ return _routes;
22
+ }
23
+
24
+ function tokenize(text) {
25
+ return String(text || '').toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
26
+ }
27
+
28
+ function scoreRoute(route, tokens) {
29
+ let score = route.priority || 1;
30
+ for (const kw of route.keywords || []) {
31
+ const parts = kw.toLowerCase().split(/\s+/);
32
+ if (parts.every((p) => tokens.some((t) => t.includes(p) || p.includes(t)))) score += 10;
33
+ else if (parts.some((p) => tokens.includes(p))) score += 4;
34
+ }
35
+ return score;
36
+ }
37
+
38
+ export function suggestSkills({ intent = '', specialistId = null, limit = 5, rootDir = ROOT, cwd = null } = {}) {
39
+ const routes = loadRoutes(rootDir);
40
+ const tokens = tokenize(intent);
41
+ const scored = routes
42
+ .map((route) => ({ ...route, score: scoreRoute(route, tokens) }))
43
+ .filter((r) => r.score > (r.priority || 1))
44
+ .sort((a, b) => b.score - a.score);
45
+
46
+ let entitled = null;
47
+ if (specialistId) {
48
+ const registry = loadRegistry({ rootDir });
49
+ const id = specialistId.startsWith('cx-') ? specialistId : `cx-${specialistId}`;
50
+ entitled = new Set(registry.specialists?.[id]?.skills || []);
51
+ }
52
+
53
+ const profile = cwd ? resolveActiveScope(cwd) : null;
54
+ const baseline = new Set(profile?.defaultSkills || []);
55
+
56
+ const manifest = loadArtifactManifest({ rootDir });
57
+ for (const entry of Object.values(manifest.artifacts || {})) {
58
+ if (entry.workflowSkill && tokens.some((t) => t.includes('prd') || t.includes('adr'))) {
59
+ scored.push({ path: entry.workflowSkill, domain: 'workflow', score: 12, priority: 12 });
60
+ }
61
+ }
62
+
63
+ const merged = new Map();
64
+ for (const item of scored) {
65
+ const prev = merged.get(item.path);
66
+ if (!prev || item.score > prev.score) merged.set(item.path, item);
67
+ }
68
+
69
+ const results = [...merged.values()]
70
+ .sort((a, b) => b.score - a.score)
71
+ .slice(0, limit)
72
+ .map((r) => ({
73
+ path: r.path,
74
+ domain: r.domain,
75
+ score: r.score,
76
+ entitled: entitled ? entitled.has(r.path) || baseline.has(r.path) : true,
77
+ }));
78
+
79
+ return { intent, suggestions: results };
80
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * lib/specialist-contracts.mjs — loader + query layer for specialists/contracts.json.
2
+ * lib/specialist-contracts.mjs — loader + query layer for specialists/org contracts.
3
3
  *
4
4
  * Agent contracts are explicit service contracts between Construct roles —
5
5
  * "when cx-product-manager hands off to cx-architect, the packet MUST contain
@@ -14,27 +14,15 @@
14
14
  * to introspect their inputs and expected outputs at handoff time.
15
15
  * - future: handoff validators that check packet shape before dispatch.
16
16
  */
17
- import { existsSync, readFileSync } from 'node:fs';
18
- import { dirname, join, resolve } from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
22
- const REPO_ROOT = resolve(MODULE_DIR, '..');
23
- const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
17
+ import { loadRegistry } from './registry/loader.mjs';
24
18
 
25
19
  let _cache = null;
26
20
 
27
21
  function loadRaw() {
28
22
  if (_cache) return _cache;
29
- if (!existsSync(CONTRACTS_PATH)) {
30
- _cache = { version: 0, contracts: [] };
31
- return _cache;
32
- }
33
- try {
34
- _cache = JSON.parse(readFileSync(CONTRACTS_PATH, 'utf8'));
35
- } catch (err) {
36
- _cache = { version: 0, contracts: [], parseError: err?.message || String(err) };
37
- }
23
+ const registry = loadRegistry();
24
+ const contracts = Object.values(registry.contracts || {});
25
+ _cache = { version: 2, contracts };
38
26
  return _cache;
39
27
  }
40
28
 
@@ -46,6 +34,13 @@ export function getContractById(id) {
46
34
  return getAllContracts().find((c) => c.id === id) || null;
47
35
  }
48
36
 
37
+ export function getContractSkillHints(contractOrId) {
38
+ const contract = typeof contractOrId === 'string'
39
+ ? getContractById(contractOrId)
40
+ : contractOrId;
41
+ return Array.isArray(contract?.skillHints) ? [...contract.skillHints] : [];
42
+ }
43
+
49
44
  /**
50
45
  * Returns the contract for a specific producer→consumer pair. If multiple
51
46
  * contracts match (e.g. wildcard producer "*"), returns the most specific one.
@@ -152,7 +147,11 @@ export function resolveContractChain(ctx = {}) {
152
147
  const stage = contract.trigger?.postConsumer ? 'followup'
153
148
  : contract.producer === 'user' ? 'precheck'
154
149
  : 'handoff';
155
- chain.push({ contract, stage });
150
+ chain.push({
151
+ contract,
152
+ stage,
153
+ skillHints: getContractSkillHints(contract),
154
+ });
156
155
  }
157
156
  return chain;
158
157
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/specialists/postconditions.mjs — hard binary postconditions per producer persona.
3
3
  *
4
- * `specialists/contracts.json` carries free-text postconditions for documentation
4
+ * `specialists/org` carries free-text postconditions for documentation
5
5
  * purposes. This module adds binary, programmatically-validated assertions
6
6
  * that the persona's output packet must satisfy before a handoff is allowed
7
7
  * to proceed. Failure throws `ContractViolationError` with reason
@@ -40,7 +40,7 @@ function isLaterOrEqual(a, b) {
40
40
  }
41
41
 
42
42
  /**
43
- * Rule table. Keyed by producer name (matching specialists/registry.json).
43
+ * Rule table. Keyed by producer name (matching specialists/org).
44
44
  * Each rule:
45
45
  * id — stable identifier for violation logs
46
46
  * description — single-line documentation
@@ -17,6 +17,7 @@
17
17
  import fs from 'node:fs';
18
18
  import path from 'node:path';
19
19
  import yaml from 'js-yaml';
20
+ import { loadRegistry as loadRuntimeRegistry } from '../registry/loader.mjs';
20
21
 
21
22
  export const REQUIRED_FRONTMATTER = ['name', 'role', 'version', 'perspective'];
22
23
  export const PERSPECTIVE_FIELDS = ['bias', 'tension', 'openingQuestion', 'failureMode'];
@@ -124,8 +125,8 @@ export function validatePromptFiles({ rootDir = process.cwd(), registry } = {})
124
125
  let total = 0;
125
126
  let converted = 0;
126
127
 
127
- const reg = registry ?? loadRegistry(rootDir);
128
- const specialists = Array.isArray(reg?.specialists) ? reg.specialists : [];
128
+ const reg = registry ?? loadPromptRegistry(rootDir);
129
+ const specialists = Array.isArray(reg?.specialists) ? reg.specialists : Object.values(reg?.specialists || {});
129
130
  const byName = new Map(specialists.map((s) => [s.name, s]));
130
131
 
131
132
  const seen = new Set();
@@ -145,9 +146,9 @@ export function validatePromptFiles({ rootDir = process.cwd(), registry } = {})
145
146
  return { errors, warnings, total, converted };
146
147
  }
147
148
 
148
- function loadRegistry(rootDir) {
149
+ function loadPromptRegistry(rootDir) {
149
150
  try {
150
- return JSON.parse(fs.readFileSync(path.join(rootDir, 'specialists', 'registry.json'), 'utf8'));
151
+ return loadRuntimeRegistry({ rootDir });
151
152
  } catch {
152
153
  return { specialists: [] };
153
154
  }
@@ -159,7 +160,7 @@ function loadRegistry(rootDir) {
159
160
  * @returns {object|null}
160
161
  */
161
162
  export function resolvePerspectiveFromPrompt(agentName, { rootDir = process.cwd(), registry } = {}) {
162
- const reg = registry ?? loadRegistry(rootDir);
163
+ const reg = registry ?? loadPromptRegistry(rootDir);
163
164
  const normalized = String(agentName ?? '').trim().replace(/^cx-/, '');
164
165
  const entry = (reg.specialists ?? []).find((s) => s.name === normalized);
165
166
  if (!entry?.promptFile) return null;
@@ -6,17 +6,10 @@
6
6
  * than inlined into synced orchestrator prompts. Each catalog entry carries id and
7
7
  * whenToUse for routing discovery without an always-on prompt block.
8
8
  */
9
- import { readFileSync } from 'node:fs';
10
- import { dirname, join } from 'node:path';
11
- import { fileURLToPath } from 'node:url';
9
+ import { loadRegistry } from '../registry/loader.mjs';
12
10
 
13
- const REGISTRY_PATH = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'specialists', 'registry.json');
14
-
15
- function loadRegistry(rootDir = null) {
16
- const path = rootDir
17
- ? join(rootDir, 'specialists', 'registry.json')
18
- : REGISTRY_PATH;
19
- return JSON.parse(readFileSync(path, 'utf8'));
11
+ function loadRegistryForRoot(rootDir = null) {
12
+ return loadRegistry(rootDir ? { rootDir } : {});
20
13
  }
21
14
 
22
15
  /**
@@ -24,9 +17,11 @@ function loadRegistry(rootDir = null) {
24
17
  * the cx-prefixed id and the routing hint (when_to_use when present, else description).
25
18
  */
26
19
  export function buildSpecialistCatalog({ rootDir = null, includeInternal = true } = {}) {
27
- const registry = loadRegistry(rootDir);
20
+ const registry = loadRegistryForRoot(rootDir);
28
21
  const prefix = registry.prefix || 'cx';
29
- const specialists = Array.isArray(registry.specialists) ? registry.specialists : [];
22
+ const specialists = Array.isArray(registry.specialists)
23
+ ? registry.specialists
24
+ : Object.values(registry.specialists || {});
30
25
  return specialists
31
26
  .filter((s) => includeInternal || !s.internal)
32
27
  .map((s) => ({
@@ -1,5 +1,5 @@
1
1
  /**
2
- * lib/specialists/schema.mjs — validation contract for specialists/registry.json entries.
2
+ * lib/specialists/schema.mjs — validation contract for specialists/org entries.
3
3
  *
4
4
  * Catches drift before it ships: missing description, unknown tool names,
5
5
  * promptFile pointing at a path that does not resolve, unrecognized
@@ -23,7 +23,7 @@ export const ALLOWED_TOOLS = new Set([
23
23
  'WebSearch', 'WebFetch', 'TodoWrite', 'NotebookEdit', 'Task',
24
24
  // Construct MCP tools (from construct-mcp server)
25
25
  'list_skills', 'get_skill', 'search_skills',
26
- 'list_teams', 'get_team',
26
+ 'list_teams', 'suggest_skills',
27
27
  'list_templates', 'get_template',
28
28
  'agent_contract', 'orchestration_policy',
29
29
  'cx_trace', 'cx_score',
@@ -90,13 +90,16 @@ export function validateAgentRecord(record, { rootDir, registryKey } = {}) {
90
90
  * Validate every agent in a registry. Returns { errors: string[], agentCount }.
91
91
  */
92
92
  export function validateRegistry(registry, { rootDir } = {}) {
93
- if (!registry || !Array.isArray(registry.specialists)) {
93
+ if (!registry) {
94
94
  return { errors: ['registry.specialists is missing or not an array'], agentCount: 0 };
95
95
  }
96
+ const specialists = Array.isArray(registry.specialists)
97
+ ? registry.specialists
98
+ : Object.values(registry.specialists || {});
96
99
  const errors = [];
97
100
  const seenNames = new Set();
98
- for (let i = 0; i < registry.specialists.length; i += 1) {
99
- const record = registry.specialists[i];
101
+ for (let i = 0; i < specialists.length; i += 1) {
102
+ const record = specialists[i];
100
103
  const recordErrors = validateAgentRecord(record, { rootDir, registryKey: i });
101
104
  errors.push(...recordErrors);
102
105
  if (record?.name) {
@@ -104,7 +107,7 @@ export function validateRegistry(registry, { rootDir } = {}) {
104
107
  seenNames.add(record.name);
105
108
  }
106
109
  }
107
- return { errors, agentCount: registry.specialists.length };
110
+ return { errors, agentCount: specialists.length };
108
111
  }
109
112
 
110
113
  /**