@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,123 +1,161 @@
1
1
  /**
2
- * lib/mcp/tools/orchestration-run.mjs — MCP client for the local orchestration daemon.
2
+ * lib/mcp/tools/orchestration-run.mjs — MCP entry for Construct orchestration.
3
3
  *
4
- * Executes a real multi-specialist run by driving the daemon's HTTP API
5
- * (ADR-0022), so MCP hosts with no subagent primitive VS Code/Copilot, Cursor
6
- * reach orchestrated outcomes through a tool they actually expose: the engine
7
- * owns orchestration; this tool is the thin client. The daemon URL resolves from
8
- * dashboard state (else the default loopback port) and the bearer token from
9
- * ~/.construct/config.env. When the daemon is unreachable the tool fails fast
10
- * with an actionable error rather than silently degrading to a single-persona
11
- * pass the absence of an executable path is made loud, not hidden.
4
+ * Runs a real multi-specialist orchestration so MCP hosts with no subagent
5
+ * primitive (VS Code/Copilot, Cursor) reach orchestrated outcomes through a tool
6
+ * they already expose. The engine is the in-process orchestration runtime
7
+ * (lib/orchestration/runtime.mjs) solo runs need no daemon, no port, no token
8
+ * (ADR-0022: the engine owns orchestration; ADR-0041 owned loop). When
9
+ * CONSTRUCT_ORCHESTRATION_URL points at a remote/team orchestration service the
10
+ * same call is proxied over HTTP instead, with the bearer token resolved from the
11
+ * Construct env (CONSTRUCT_ORCHESTRATION_TOKEN / CONSTRUCT_DASHBOARD_TOKEN), not
12
+ * from the dashboard server.
12
13
  */
13
14
 
14
- import { readDashboardState } from '../../service-manager.mjs';
15
- import { getDashboardToken } from '../../server/auth.mjs';
15
+ import { runOrchestration, startRun, getRun, getRuns } from '../../orchestration/runtime.mjs';
16
16
 
17
17
  const TERMINAL = ['completed', 'completed-with-failures', 'cancelled', 'error'];
18
18
 
19
- function resolveDaemon(env = process.env) {
20
- const state = readDashboardState();
21
- const base = (env.CONSTRUCT_ORCHESTRATION_URL || state?.url || `http://${env.BIND_HOST || '127.0.0.1'}:${env.PORT || '4242'}`).replace(/\/$/, '');
22
- return { base, token: getDashboardToken(), daemonRunning: Boolean(state) };
19
+ function shapeRun(run) {
20
+ return {
21
+ runId: run.runId,
22
+ status: run.status,
23
+ executionMode: run.execution?.executionMode,
24
+ degraded: run.execution?.degraded ?? false,
25
+ degradationReason: run.execution?.degradationReason ?? null,
26
+ intent: run.plan?.intent ?? null,
27
+ track: run.plan?.track ?? null,
28
+ suggestedWorkflowType: run.plan?.suggestedWorkflowType ?? null,
29
+ researchExecutionPolicy: run.plan?.researchExecutionPolicy ?? null,
30
+ specialists: run.plan?.specialists ?? [],
31
+ tasks: (run.tasks || []).map((t) => ({
32
+ id: t.id, role: t.role, status: t.status, executor: t.executor,
33
+ output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null,
34
+ })),
35
+ };
36
+ }
37
+
38
+ function toRequest(args) {
39
+ return {
40
+ request: args.request,
41
+ workflowType: args.workflow_type,
42
+ requestedStrategy: args.requested_strategy ?? 'auto',
43
+ host: args.host,
44
+ hostModel: args.host_model,
45
+ hostProvider: args.host_provider,
46
+ fileCount: args.file_count,
47
+ moduleCount: args.module_count,
48
+ };
23
49
  }
24
50
 
25
- // A reachability failure is the fail-fast signal MCP hosts need: report how to
26
- // start the engine instead of letting the host fall back to a manual pass.
51
+ // A remote orchestration service is opt-in: only when CONSTRUCT_ORCHESTRATION_URL
52
+ // is set (team / enterprise). The token comes from the Construct env, not from
53
+ // the dashboard server, so the in-process default path carries no dashboard coupling.
54
+
55
+ function remoteService(env) {
56
+ const url = env.CONSTRUCT_ORCHESTRATION_URL;
57
+ if (!url) return null;
58
+ const token = env.CONSTRUCT_ORCHESTRATION_TOKEN || env.CONSTRUCT_DASHBOARD_TOKEN || null;
59
+ return { base: url.replace(/\/$/, ''), token };
60
+ }
27
61
 
28
62
  function unreachable(base, detail) {
29
63
  return {
30
- error: `Orchestration daemon not reachable at ${base}: ${detail}. Start it with \`construct dashboard\`, then retry. The engine runs orchestration; this host is a thin client (ADR-0022).`,
31
- daemon: base,
64
+ error: `Remote orchestration service not reachable at ${base}: ${detail}. Unset CONSTRUCT_ORCHESTRATION_URL to run in-process, or start the service and retry.`,
65
+ service: base,
32
66
  failFast: true,
33
67
  };
34
68
  }
35
69
 
36
- export async function orchestrationRun(args = {}, { fetchImpl = fetch, env = process.env } = {}) {
37
- const {
38
- request,
39
- workflow_type,
40
- requested_strategy = 'auto',
41
- worker_backend,
42
- host,
43
- host_model,
44
- host_provider,
45
- file_count,
46
- module_count,
47
- wait = true,
48
- timeout_ms = 120000,
49
- } = args;
50
-
51
- if (!request || typeof request !== 'string') return { error: 'Missing "request" string describing the work to orchestrate.' };
52
-
53
- const { base, token } = resolveDaemon(env);
54
- const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
55
- const body = JSON.stringify({
56
- request,
57
- workflowType: workflow_type,
58
- requestedStrategy: requested_strategy,
59
- workerBackend: worker_backend,
60
- host,
61
- hostModel: host_model,
62
- hostProvider: host_provider,
63
- fileCount: file_count,
64
- moduleCount: module_count,
65
- });
70
+ export async function orchestrationRun(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
71
+ const { request, worker_backend, wait = true, timeout_ms = 120000 } = args;
72
+ if (!request || typeof request !== 'string') {
73
+ return { error: 'Missing "request" string describing the work to orchestrate.' };
74
+ }
75
+
76
+ const reqObj = toRequest(args);
77
+ const remote = remoteService(env);
78
+ if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl });
79
+
80
+ const opts = { env, cwd, workerBackend: worker_backend, fetchImpl };
81
+ try {
82
+ if (!wait) {
83
+ const planned = await startRun(reqObj, opts);
84
+ return { runId: planned.runId, status: planned.status, executionMode: planned.execution?.executionMode, poll: 'orchestration_status with run_id' };
85
+ }
86
+ return shapeRun(await runOrchestration(reqObj, opts));
87
+ } catch (err) {
88
+ return { error: `Orchestration failed: ${err.message}`, failFast: true };
89
+ }
90
+ }
91
+
92
+ export async function orchestrationStatus(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
93
+ const { run_id, limit = 20 } = args;
94
+ const remote = remoteService(env);
95
+ if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl });
96
+ try {
97
+ if (run_id) {
98
+ const run = await getRun(cwd, run_id, { env });
99
+ return run || { error: `No run found with id ${run_id}.` };
100
+ }
101
+ return await getRuns(cwd, { env, limit });
102
+ } catch (err) {
103
+ return { error: `Failed to read orchestration runs: ${err.message}` };
104
+ }
105
+ }
106
+
107
+ // Remote (team / enterprise) HTTP path. Reached only when CONSTRUCT_ORCHESTRATION_URL
108
+ // is set; the wire shape matches the standalone orchestration service's
109
+ // /api/orchestration/runs contract (ADR-0022).
66
110
 
111
+ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl }) {
112
+ const { base, token } = remote;
113
+ const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
114
+ const body = JSON.stringify({ ...reqObj, workerBackend });
67
115
  let started;
68
116
  try {
69
117
  const res = await fetchImpl(`${base}/api/orchestration/runs`, { method: 'POST', headers, body });
70
118
  if (res.status === 401 || res.status === 403) {
71
- return { error: `Daemon rejected the request (HTTP ${res.status}). The dashboard token is missing or wrong — check CONSTRUCT_DASHBOARD_TOKEN in ~/.construct/config.env.`, daemon: base, failFast: true };
119
+ return { error: `Remote service rejected the request (HTTP ${res.status}). Set CONSTRUCT_ORCHESTRATION_TOKEN (or CONSTRUCT_DASHBOARD_TOKEN).`, service: base, failFast: true };
72
120
  }
73
121
  const envelope = await res.json();
74
- if (!res.ok) return { error: `Daemon error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, daemon: base };
122
+ if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
75
123
  started = envelope.data;
76
124
  } catch (err) {
77
125
  return unreachable(base, err.message);
78
126
  }
79
127
 
80
128
  if (!wait) {
81
- return { runId: started.runId, status: started.status, executionMode: started.execution?.executionMode, daemon: base, poll: 'orchestration_status with run_id' };
129
+ return { runId: started.runId, status: started.status, executionMode: started.execution?.executionMode, service: base, poll: 'orchestration_status with run_id' };
82
130
  }
83
131
 
84
132
  const deadline = Date.now() + timeout_ms;
85
133
  let run = started;
86
134
  while (!TERMINAL.includes(run.status)) {
87
135
  if (Date.now() > deadline) {
88
- return { runId: started.runId, status: run.status, timedOut: true, daemon: base, note: 'Run is still executing; poll it with orchestration_status.' };
136
+ return { runId: started.runId, status: run.status, timedOut: true, service: base, note: 'Run is still executing; poll it with orchestration_status.' };
89
137
  }
90
138
  await new Promise((r) => setTimeout(r, 250));
91
139
  try {
92
140
  const res = await fetchImpl(`${base}/api/orchestration/runs/${encodeURIComponent(started.runId)}`, { headers });
93
141
  const envelope = await res.json();
94
- if (!res.ok) return { error: `Daemon error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, daemon: base };
142
+ if (!res.ok) return { error: `Remote service error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, service: base };
95
143
  run = envelope.data;
96
144
  } catch (err) {
97
- return { error: `Lost connection to the daemon while polling run ${started.runId}: ${err.message}`, runId: started.runId, daemon: base, failFast: true };
145
+ return { error: `Lost connection to the remote service while polling run ${started.runId}: ${err.message}`, runId: started.runId, service: base, failFast: true };
98
146
  }
99
147
  }
100
-
101
- return {
102
- runId: run.runId,
103
- status: run.status,
104
- executionMode: run.execution?.executionMode,
105
- degraded: run.execution?.degraded ?? false,
106
- degradationReason: run.execution?.degradationReason ?? null,
107
- tasks: (run.tasks || []).map((t) => ({ id: t.id, role: t.role, status: t.status, executor: t.executor, output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null })),
108
- daemon: base,
109
- };
148
+ return { ...shapeRun(run), service: base };
110
149
  }
111
150
 
112
- export async function orchestrationStatus(args = {}, { fetchImpl = fetch, env = process.env } = {}) {
113
- const { run_id, limit = 20 } = args;
114
- const { base, token } = resolveDaemon(env);
151
+ async function statusViaService(remote, { run_id, limit }, { fetchImpl }) {
152
+ const { base, token } = remote;
115
153
  const headers = token ? { Authorization: `Bearer ${token}` } : {};
116
154
  const path = run_id ? `/api/orchestration/runs/${encodeURIComponent(run_id)}` : `/api/orchestration/runs?limit=${encodeURIComponent(limit)}`;
117
155
  try {
118
156
  const res = await fetchImpl(`${base}${path}`, { headers });
119
157
  const envelope = await res.json();
120
- if (!res.ok) return { error: `Daemon error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, daemon: base };
158
+ if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
121
159
  return envelope.data;
122
160
  } catch (err) {
123
161
  return unreachable(base, err.message);
@@ -5,13 +5,14 @@
5
5
  * All tools are synchronous except where noted. Requires ROOT_DIR and homedir from the caller.
6
6
  */
7
7
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
8
+ import { loadRegistry } from '../../registry/loader.mjs';
8
9
  import { join, resolve } from 'node:path';
9
10
  import { homedir } from 'node:os';
10
11
  import { execSync } from 'node:child_process';
11
12
  import { loadConstructEnv } from '../../env-config.mjs';
12
13
  import { inspectContextState } from '../../context-state.mjs';
13
14
  import { loadWorkflow, summarizeWorkflow, inspectWorkflowHealth } from '../../workflow-state.mjs';
14
- import { readCurrentModels, resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../../model-router.mjs';
15
+ import { resolveExecutionContractModelMetadata, selectModelTierForWorkCategory } from '../../model-router.mjs';
15
16
  import { buildPublicHealthSurface } from '../../status.mjs';
16
17
  import { doctorRoot } from '../../config/xdg.mjs';
17
18
 
@@ -282,7 +283,7 @@ export function projectContext(args, { ROOT_DIR }) {
282
283
  const has_context_file = contextInspection.hasFile;
283
284
  const workflow = loadWorkflow(cwd);
284
285
  const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
285
- const registry = readJSON(join(ROOT_DIR, 'specialists', 'registry.json')) ?? {};
286
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
286
287
  const executionContractModel = resolveExecutionContractModelMetadata({
287
288
  envValues: mergedEnv,
288
289
  registryModels: registry.models ?? {},
@@ -324,7 +325,7 @@ export function workflowStatus(args, { ROOT_DIR }) {
324
325
  const workflow = loadWorkflow(cwd);
325
326
  const workflowHealth = inspectWorkflowHealth(workflow, { cwd });
326
327
  const mergedEnv = loadConstructEnv({ rootDir: ROOT_DIR, homeDir: homedir(), env: process.env });
327
- const registry = readJSON(join(ROOT_DIR, 'specialists', 'registry.json')) ?? {};
328
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
328
329
  const executionContractModel = resolveExecutionContractModelMetadata({
329
330
  envValues: mergedEnv,
330
331
  registryModels: registry.models ?? {},
@@ -1,7 +1,7 @@
1
1
  /**
2
- * lib/mcp/tools/profile.mjs — MCP wrappers for the org-profile + learning surfaces.
2
+ * lib/mcp/tools/scope.mjs — MCP wrappers for the org-scope + learning surfaces.
3
3
  *
4
- * Bridges PR #67 capabilities (profiles, outcomes, sandboxes, knowledge_add,
4
+ * Bridges PR #67 capabilities (scopes, outcomes, sandboxes, knowledge_add,
5
5
  * learning status) so subagents talking through the construct-mcp server can
6
6
  * read the same state the CLI exposes. Operator-only surfaces (optimize_apply,
7
7
  * optimize_rollback) remain CLI-only and are not registered here.
@@ -9,7 +9,7 @@
9
9
  * Pattern: each export is a thin schema-validated wrapper around the existing
10
10
  * lib/* function the CLI already uses. Errors return `{ error: string }` so
11
11
  * the MCP dispatcher hands a structured failure back instead of crashing.
12
- * Destructive operations (profile_archive) require `confirm: true`, matching
12
+ * Destructive operations (scope_archive) require `confirm: true`, matching
13
13
  * the storage_reset / delete_ingested_artifacts pattern in tools/storage.mjs.
14
14
  */
15
15
  import { resolve } from 'node:path';
@@ -17,17 +17,17 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
 
19
19
  import {
20
- loadProfile,
21
- listProfiles,
22
- loadCustomProfile,
23
- resolveActiveProfile,
24
- } from '../../profiles/loader.mjs';
20
+ loadScope,
21
+ listScopes,
22
+ loadCustomScope,
23
+ resolveActiveScope,
24
+ } from '../../scopes/loader.mjs';
25
25
  import {
26
- createDraftProfile,
26
+ createDraftScope,
27
27
  listDrafts,
28
- archiveProfile,
29
- profileHealth,
30
- } from '../../profiles/lifecycle.mjs';
28
+ archiveScope,
29
+ scopeHealth,
30
+ } from '../../scopes/lifecycle.mjs';
31
31
  import { recordOutcome } from '../../outcomes/record.mjs';
32
32
  import { aggregateOutcomes, readSummary } from '../../outcomes/aggregate.mjs';
33
33
  import { addResearchFinding } from '../../knowledge/research-store.mjs';
@@ -38,73 +38,75 @@ function cwdOf(args) {
38
38
  return args && args.cwd ? resolve(String(args.cwd)) : process.cwd();
39
39
  }
40
40
 
41
- // Read-only: active profile.
41
+ // Read-only: active scope.
42
42
 
43
- export function profileShow(args = {}) {
43
+ export function scopeShow(args = {}) {
44
44
  const cwd = cwdOf(args);
45
- const profile = resolveActiveProfile(cwd, args.id ? String(args.id) : null);
45
+ const scope = resolveActiveScope(cwd, args.id ? String(args.id) : null);
46
46
  return {
47
- id: profile.id,
48
- displayName: profile.displayName ?? profile.id,
49
- tagline: profile.tagline ?? null,
50
- custom: profile.custom === true,
51
- roles: Array.isArray(profile.roles) ? profile.roles : [],
52
- departments: Array.isArray(profile.departments)
53
- ? profile.departments.map((d) => ({ id: d.id, displayName: d.displayName, roleCount: Array.isArray(d.roles) ? d.roles.length : 0 }))
47
+ id: scope.id,
48
+ displayName: scope.displayName ?? scope.id,
49
+ tagline: scope.tagline ?? null,
50
+ custom: scope.custom === true,
51
+ roles: Array.isArray(scope.roles) ? scope.roles : [],
52
+ teams: Array.isArray(scope.teams)
53
+ ? scope.teams.map((t) => ({ id: t.id, name: t.name, roleCount: Array.isArray(t.roles) ? t.roles.length : 0 }))
54
54
  : [],
55
+ experimental: scope.experimental === true,
55
56
  intake: {
56
- types: profile.intake?.types ?? [],
57
- stages: profile.intake?.stages ?? [],
57
+ types: scope.intake?.types ?? [],
58
+ stages: scope.intake?.stages ?? [],
58
59
  },
59
- docTemplates: profile.docTemplates ?? [],
60
- hooks: profile.hooks ?? null,
61
- rebrand: profile.rebrand ?? null,
60
+ docTemplates: scope.docTemplates ?? [],
61
+ hooks: scope.hooks ?? null,
62
+ rebrand: scope.rebrand ?? null,
62
63
  };
63
64
  }
64
65
 
65
66
  // Read-only: catalog summary.
66
67
 
67
- export function profileList() {
68
+ export function scopeList() {
68
69
  return {
69
- profiles: listProfiles().map((id) => {
70
- const p = loadProfile(id);
70
+ scopes: listScopes().map((id) => {
71
+ const p = loadScope(id);
71
72
  return p
72
73
  ? {
73
74
  id: p.id,
74
75
  displayName: p.displayName ?? p.id,
75
76
  tagline: p.tagline ?? null,
76
77
  roleCount: Array.isArray(p.roles) ? p.roles.length : 0,
77
- departmentCount: Array.isArray(p.departments) ? p.departments.length : 0,
78
+ teamCount: Array.isArray(p.teams) ? p.teams.length : 0,
79
+ experimental: p.experimental === true,
78
80
  }
79
81
  : { id, error: 'failed to parse' };
80
82
  }),
81
83
  };
82
84
  }
83
85
 
84
- // Read-only: drafts + custom profile under .cx/.
86
+ // Read-only: drafts + custom scope under .cx/.
85
87
 
86
- export function profileDrafts(args = {}) {
88
+ export function scopeDrafts(args = {}) {
87
89
  const cwd = cwdOf(args);
88
90
  const drafts = listDrafts(cwd).map((d) => ({
89
91
  id: d.id,
90
92
  dir: d.dir,
91
- hasProfile: d.hasProfile,
93
+ hasScope: d.hasScope,
92
94
  hasBrief: d.hasBrief,
93
95
  }));
94
- const custom = loadCustomProfile(cwd);
96
+ const custom = loadCustomScope(cwd);
95
97
  return {
96
98
  drafts,
97
99
  custom: custom ? { id: custom.id, displayName: custom.displayName ?? custom.id } : null,
98
100
  };
99
101
  }
100
102
 
101
- // Read-only: per-profile health rollup.
103
+ // Read-only: per-scope health rollup.
102
104
 
103
- export function profileHealthTool(args = {}) {
105
+ export function scopeHealthTool(args = {}) {
104
106
  const cwd = cwdOf(args);
105
- const id = args.id ? String(args.id) : resolveActiveProfile(cwd).id;
107
+ const id = args.id ? String(args.id) : resolveActiveScope(cwd).id;
106
108
  const windowDays = Number.isFinite(args.window_days) ? Number(args.window_days) : 30;
107
- return { id, windowDays, ...profileHealth(cwd, id, { windowDays }) };
109
+ return { id, windowDays, ...scopeHealth(cwd, id, { windowDays }) };
108
110
  }
109
111
 
110
112
  // Read-only: outcomes rollup. Optional aggregate=true rebuilds _summary.json.
@@ -129,7 +131,7 @@ export function outcomesRecord(args = {}) {
129
131
  if (!args.role || typeof args.success !== 'boolean') {
130
132
  return { error: 'outcomes_record requires role:string and success:boolean' };
131
133
  }
132
- const profile = args.profile ? String(args.profile) : resolveActiveProfile(cwd)?.id;
134
+ const profile = args.profile ? String(args.profile) : resolveActiveScope(cwd)?.id;
133
135
  const file = recordOutcome(cwd, {
134
136
  role: String(args.role),
135
137
  intakeId: args.intake_id ? String(args.intake_id) : null,
@@ -167,16 +169,16 @@ export async function knowledgeAdd(args = {}) {
167
169
  }
168
170
  }
169
171
 
170
- // Mutating, gated: scaffold a draft profile.
172
+ // Mutating, gated: scaffold a draft scope.
171
173
 
172
- export function profileCreate(args = {}) {
174
+ export function scopeCreate(args = {}) {
173
175
  if (args.confirm !== true) {
174
- return { error: 'profile_create requires confirm=true to scaffold files' };
176
+ return { error: 'scope_create requires confirm=true to scaffold files' };
175
177
  }
176
178
  const cwd = cwdOf(args);
177
- if (!args.id) return { error: 'profile_create requires id:string' };
179
+ if (!args.id) return { error: 'scope_create requires id:string' };
178
180
  try {
179
- const res = createDraftProfile({
181
+ const res = createDraftScope({
180
182
  cwd,
181
183
  id: String(args.id),
182
184
  displayName: args.display_name ? String(args.display_name) : undefined,
@@ -196,17 +198,17 @@ export function profileCreate(args = {}) {
196
198
  }
197
199
  }
198
200
 
199
- // Mutating, gated, destructive: archive a curated profile.
201
+ // Mutating, gated, destructive: archive a curated scope.
200
202
 
201
- export function profileArchive(args = {}) {
203
+ export function scopeArchive(args = {}) {
202
204
  if (args.confirm !== true) {
203
- return { error: 'profile_archive requires confirm=true (destructive: moves files into archive/profiles/)' };
205
+ return { error: 'scope_archive requires confirm=true (destructive: moves files into archive/scopes/)' };
204
206
  }
205
207
  if (!args.id || !args.reason || String(args.reason).trim().length < 8) {
206
- return { error: 'profile_archive requires id:string and reason:string (>=8 chars)' };
208
+ return { error: 'scope_archive requires id:string and reason:string (>=8 chars)' };
207
209
  }
208
210
  try {
209
- return { ok: true, ...archiveProfile({ id: String(args.id), reason: String(args.reason) }) };
211
+ return { ok: true, ...archiveScope({ id: String(args.id), reason: String(args.reason) }) };
210
212
  } catch (err) {
211
213
  return { error: err.message ?? String(err) };
212
214
  }
@@ -258,11 +260,11 @@ export function learningStatus(args = {}) {
258
260
  }
259
261
  } catch { /* best effort */ }
260
262
 
261
- const profile = resolveActiveProfile(cwd);
263
+ const scope = resolveActiveScope(cwd);
262
264
  const outcomes = readSummary(cwd) || { roles: {} };
263
265
 
264
266
  return {
265
- profile: { id: profile.id, displayName: profile.displayName ?? profile.id, custom: profile.custom === true },
267
+ scope: { id: scope.id, displayName: scope.displayName ?? scope.id, custom: scope.custom === true },
266
268
  observations,
267
269
  research: { count: researchCount },
268
270
  outcomes,
@@ -19,6 +19,9 @@ import { buildTaskPacketFromIntent } from '../../workflow-state.mjs';
19
19
  import { logSkillCall } from '../../telemetry/skill-calls.mjs';
20
20
  import { templateMetadata } from '../../artifact-manifest.mjs';
21
21
  import { buildSpecialistCatalog } from '../../specialists/roster.mjs';
22
+ import { loadRegistry } from '../../registry/loader.mjs';
23
+ import { listGroupsHierarchical } from '../../registry/assemble.mjs';
24
+ import { suggestSkills } from '../../skills/router.mjs';
22
25
 
23
26
  export function listSkills({ ROOT_DIR }) {
24
27
  const skillsDir = join(ROOT_DIR, 'skills');
@@ -48,9 +51,22 @@ export function listSkills({ ROOT_DIR }) {
48
51
  }
49
52
 
50
53
  export function getSkill(args, { ROOT_DIR }) {
51
- const { path: skillPath } = args;
54
+ const { path: skillPath, specialistId, agentId } = args;
52
55
  if (!skillPath) return { error: 'Missing path argument' };
53
56
 
57
+ const strict = process.env.CONSTRUCT_STRICT_SKILLS === '1';
58
+ const specId = specialistId || agentId;
59
+ let entitlementWarning = null;
60
+ if (specId) {
61
+ const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
62
+ const id = String(specId).startsWith('cx-') ? specId : `cx-${specId}`;
63
+ const entitled = new Set(registry.specialists?.[id]?.skills || []);
64
+ if (entitled.size > 0 && !entitled.has(skillPath)) {
65
+ entitlementWarning = `Skill ${skillPath} is not in ${id} entitlement list`;
66
+ if (strict) return { error: entitlementWarning };
67
+ }
68
+ }
69
+
54
70
  // Primary: flat file form skills/<path>.md
55
71
  // Fallback: directory form skills/<path>/SKILL.md (one minor-release grace period)
56
72
  const candidates = [
@@ -73,7 +89,7 @@ export function getSkill(args, { ROOT_DIR }) {
73
89
  sessionId: args.sessionId,
74
90
  tokensReturned: Math.ceil(content.length / 4),
75
91
  });
76
- return { content };
92
+ return { content, ...(entitlementWarning ? { warning: entitlementWarning } : {}) };
77
93
  }
78
94
 
79
95
  export function searchSkills(args, { ROOT_DIR }) {
@@ -434,29 +450,30 @@ export async function workerRun(args = {}) {
434
450
  return { ...result, ...(evidence ? { evidence } : {}) };
435
451
  }
436
452
 
437
- export function listTeams({ ROOT_DIR }) {
438
- const teamsPath = join(ROOT_DIR, 'specialists', 'teams.json');
439
- if (!existsSync(teamsPath)) return { error: 'specialists/teams.json not found' };
440
- const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
453
+ export function suggestSkillsTool(args, { ROOT_DIR, cwd } = {}) {
454
+ const intent = args?.intent || args?.query || '';
455
+ return suggestSkills({
456
+ intent,
457
+ specialistId: args?.specialistId || args?.agentId,
458
+ limit: args?.limit || 5,
459
+ rootDir: ROOT_DIR,
460
+ cwd: cwd || ROOT_DIR,
461
+ });
462
+ }
463
+
464
+ export function listTeams({ ROOT_DIR } = {}) {
465
+ const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
441
466
  return {
442
- teams: Object.entries(templates).map(([name, t]) => ({
443
- name,
444
- description: t.description,
445
- members: t.members,
446
- focus: t.focus,
447
- skills: t.skills,
448
- promotionGates: t.promotionGates,
449
- })),
467
+ groups: listGroupsHierarchical(registry),
468
+ teams: Object.entries(registry.teams || {}).map(([id, t]) => ({ id, ...t })),
450
469
  };
451
470
  }
452
471
 
453
- export function getTeam(args, { ROOT_DIR }) {
454
- const { name } = args;
472
+ export function getTeam(args, { ROOT_DIR } = {}) {
473
+ const name = args?.name || args?.id;
455
474
  if (!name) return { error: 'Missing name argument' };
456
- const teamsPath = join(ROOT_DIR, 'specialists', 'teams.json');
457
- if (!existsSync(teamsPath)) return { error: 'specialists/teams.json not found' };
458
- const { templates } = JSON.parse(readFileSync(teamsPath, 'utf8'));
459
- const team = templates[name];
460
- if (!team) return { error: `Team not found: ${name}`, available: Object.keys(templates) };
461
- return { name, ...team };
475
+ const registry = loadRegistry({ rootDir: ROOT_DIR, skipValidation: true });
476
+ const team = registry.teams?.[name];
477
+ if (!team) return { error: `Team not found: ${name}`, available: Object.keys(registry.teams || {}) };
478
+ return { id: name, ...team };
462
479
  }
@@ -6,6 +6,7 @@
6
6
  * adapter when configured; local JSONL remains available by default.
7
7
  */
8
8
  import { join, resolve } from 'node:path';
9
+ import { loadRegistry } from '../../registry/loader.mjs';
9
10
  import { homedir } from 'node:os';
10
11
  import { readFileSync, existsSync } from 'node:fs';
11
12
  import { createTelemetryClient } from '../../telemetry/client.mjs';
@@ -71,7 +72,7 @@ function resolveSessionContext() {
71
72
 
72
73
  export async function cxTrace(args, { ROOT_DIR }) {
73
74
  const ctx = resolveSessionContext();
74
- const registry = readJSON(join(ROOT_DIR, 'specialists', 'registry.json')) ?? {};
75
+ const registry = loadRegistry({ rootDir: ROOT_DIR });
75
76
  const registryModels = registry.models ?? {};
76
77
  const currentModels = readCurrentModels(join(ROOT_DIR, '.env'), registryModels, process.env);
77
78
  const route = typeof args.input === 'string' ? routeRequest({ request: args.input }) : null;
@@ -97,7 +97,7 @@ export function workflowValidate(args) {
97
97
  }
98
98
 
99
99
  /**
100
- * Validate a producer→consumer handoff against specialists/contracts.json.
100
+ * Validate a producer→consumer handoff against specialists/org.
101
101
  *
102
102
  * Args:
103
103
  * producer — agent or persona name producing the artifact
@@ -14,16 +14,16 @@ export function getOpenCodeMcpId(id) {
14
14
  return id;
15
15
  }
16
16
 
17
- function resolveTemplateString(value, resolvedValues) {
18
- return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => resolvedValues[name] ?? `__${name}__`);
17
+ function resolveTemplateString(value, resolvedValues, fallback = (name) => `__${name}__`) {
18
+ return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => resolvedValues[name] ?? fallback(name));
19
19
  }
20
20
 
21
- function resolveTemplateObject(input, resolvedValues) {
21
+ function resolveTemplateObject(input, resolvedValues, fallback) {
22
22
  if (!input || typeof input !== "object" || Array.isArray(input)) return {};
23
23
  return Object.fromEntries(
24
24
  Object.entries(input).map(([key, value]) => [
25
25
  key,
26
- typeof value === "string" ? resolveTemplateString(value, resolvedValues) : value,
26
+ typeof value === "string" ? resolveTemplateString(value, resolvedValues, fallback) : value,
27
27
  ]),
28
28
  );
29
29
  }
@@ -45,7 +45,9 @@ function buildLocalEnvironment(mcpDef, resolvedValues) {
45
45
  }
46
46
 
47
47
  function buildRemoteHeaders(mcpDef, resolvedValues) {
48
- return stripUnresolvedValues(resolveTemplateObject(mcpDef.headers ?? {}, resolvedValues));
48
+ return stripUnresolvedValues(
49
+ resolveTemplateObject(mcpDef.headers ?? {}, resolvedValues, (name) => `{env:${name}}`),
50
+ );
49
51
  }
50
52
 
51
53
  function withMemoryDefaults(id, values) {