@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
@@ -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
@@ -3,8 +3,8 @@
3
3
  * lib/setup.mjs — Machine-scoped first-run setup wizard for Construct.
4
4
  *
5
5
  * Installs cm/cass, configures managed defaults, pre-warms the embedding model,
6
- * wires MCP integrations, and writes ~/.construct/config.env.
7
- * Invoked by `construct install` and by lib/install/first-invocation.mjs
6
+ * wires MCP integrations, and writes the XDG user config (~/.config/construct/config.env).
7
+ * Invoked by `construct install` and by lib/install/first-invocation.mjs
8
8
  * when a TTY user is missing resources.
9
9
  *
10
10
  * Project-scoped scaffolding (`.cx/`, AGENTS.md, plan.md, adapters) lives
@@ -22,6 +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, migrateLegacyCredentialConfig } from './config/legacy-config-migration.mjs';
25
26
  import { configDir, stateDir, doctorRoot } from './config/xdg.mjs';
26
27
  import { DEFAULT_WORKSPACE_PATH, WORKSPACE_DOCS_LANES } from './embed/config.mjs';
27
28
  import {
@@ -34,8 +35,6 @@ import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embedding
34
35
  import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
35
36
  import { consentToInstall } from './setup-prompts.mjs';
36
37
  import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
37
- import { downloadDesktopBinary } from './install/desktop-binary-download.mjs';
38
- import { buildDesktopBinary, hasDesktopSource } from './chat/desktop-build.mjs';
39
38
 
40
39
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
41
40
  const HOME = os.homedir();
@@ -65,7 +64,7 @@ Flags:
65
64
  before setup; does not uninstall a global construct CLI
66
65
 
67
66
  What --scope=user does:
68
- - creates ~/.construct/config.env
67
+ - creates ~/.config/construct/config.env
69
68
  - ensures OpenCode config exists
70
69
  - configures managed defaults for local vector retrieval (LanceDB)
71
70
  - checks required runtime tools and installs cm and cass when available
@@ -354,7 +353,6 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupL
354
353
  console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
355
354
  console.log('\nWould check / install runtime tooling:');
356
355
  console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
357
- console.log(` · construct-chat desktop binary (from GitHub releases → ${path.join(stateDir(homeDir), 'bin')})`);
358
356
  console.log('\nWould reach the network / external services:');
359
357
  console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
360
358
  if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
@@ -408,6 +406,19 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
408
406
  return;
409
407
  }
410
408
 
409
+ // Forward-migrate model tier overrides stranded in the pre-XDG legacy config
410
+ // before any legacy-global cleanup deletes ~/.construct, so an upgrade keeps
411
+ // the user's tier selection without a manual copy.
412
+ const modelMigration = migrateLegacyModelConfig({ homeDir });
413
+ if (modelMigration.performed) {
414
+ console.log(`Migrated ${Object.keys(modelMigration.migrated).join(', ')} from ${modelMigration.legacyPath} → ${modelMigration.xdgPath}`);
415
+ }
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
+
411
422
  if (cleanupLegacyGlobal) {
412
423
  runLegacyGlobalCleanup({ homeDir, dryRun: false });
413
424
  if (scope === 'project') {
@@ -457,28 +468,6 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
457
468
  if (cassInstall.installCommand) console.log(` Install with: ${cassInstall.installCommand}`);
458
469
  }
459
470
 
460
- try {
461
- if (hasDesktopSource(ROOT_DIR)) {
462
- const built = buildDesktopBinary({ repoRoot: ROOT_DIR, env: process.env });
463
- if (built.ok) {
464
- console.log(`Desktop binary: built from source → ${built.binaryPath}`);
465
- } else {
466
- console.log(`Desktop binary: build skipped (${built.reason}) — runs on first \`construct chat\``);
467
- }
468
- } else {
469
- const desktop = await downloadDesktopBinary({ homeDir, env: process.env });
470
- if (desktop.status === 'downloaded') {
471
- console.log(`Desktop binary: ${desktop.version} → ${desktop.dest}`);
472
- } else if (desktop.status === 'failed') {
473
- console.log(`Desktop binary: download failed (${desktop.reason}) — run \`construct install --scope=user\` again to retry`);
474
- } else {
475
- console.log(`Desktop binary: ${desktop.reason}`);
476
- }
477
- }
478
- } catch (err) {
479
- console.log(`Desktop binary: skipped (${err?.message || 'unknown error'})`);
480
- }
481
-
482
471
  const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
483
472
  ? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
484
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) => ({