@geraldmaron/construct 1.2.2 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
package/lib/parity.mjs CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * After `construct sync` writes adapters to multiple surfaces (Claude Code,
5
5
  * OpenCode, Codex, Copilot, VS Code, Cursor), the module diffs each surface's actual state against the
6
- * canonical `specialists/registry.json`. Backs `construct doctor` for surfacing
6
+ * canonical `specialists/org`. Backs `construct doctor` for surfacing
7
7
  * silent divergence — for instance, an agent added to the registry that
8
8
  * never made it to OpenCode because of a sync regression.
9
9
  *
@@ -62,9 +62,10 @@ function readJsoncFile(file) {
62
62
  return parseJsonc(fs.readFileSync(file, 'utf8'));
63
63
  }
64
64
 
65
+ import { loadRegistry as loadAssembledRegistry } from './registry/loader.mjs';
66
+
65
67
  function loadRegistry(rootDir = ROOT_DIR) {
66
- const file = path.join(rootDir, 'specialists', 'registry.json');
67
- return JSON.parse(fs.readFileSync(file, 'utf8'));
68
+ return loadAssembledRegistry({ rootDir });
68
69
  }
69
70
 
70
71
  function adapterName(entry, prefix) {
@@ -74,18 +75,30 @@ function adapterName(entry, prefix) {
74
75
  function entriesForSurface(registry, surface, { scope = 'global' } = {}) {
75
76
  const prefix = registry.prefix || 'cx';
76
77
 
78
+ // Support both legacy format (registry.orchestrator) and unified format
79
+ // (orchestrator lives in registry.specialists with role:'orchestrator').
80
+ // Unified format stores name:'orchestrator' but the adapter name is 'construct'
81
+ // (hardcoded in unifiedToLegacyRegistry) — normalize it here so parity matches.
82
+ const rawOrchestrator = registry.orchestrator
83
+ || Object.values(registry.specialists || {}).find((s) => s.role === 'orchestrator');
84
+ const orchestratorEntry = rawOrchestrator && rawOrchestrator.name !== 'construct'
85
+ ? { ...rawOrchestrator, name: 'construct' }
86
+ : rawOrchestrator;
87
+
77
88
  // User-scope expects only the `construct` front-door agent. Specialists
78
- // live with each Construct-managed project (apps/dashboard, apps/docs,
89
+ // live with each Construct-managed project (apps/docs,
79
90
  // and every other repo that runs `construct init`) — see the two-tier
80
91
  // sync contract in scripts/sync-specialists.mjs.
81
92
 
82
93
  const entries = scope === 'project'
83
94
  ? [
84
- ...(registry.orchestrator ? [{ ...registry.orchestrator, isPersona: true }] : []),
85
- ...(registry.specialists || []).map((s) => ({ ...s, isPersona: false })),
95
+ ...(orchestratorEntry ? [{ ...orchestratorEntry, isPersona: true }] : []),
96
+ ...Object.values(registry.specialists || {})
97
+ .filter((s) => s.role !== 'orchestrator')
98
+ .map((s) => ({ ...s, isPersona: false })),
86
99
  ]
87
- : registry.orchestrator
88
- ? [{ ...registry.orchestrator, isPersona: true }]
100
+ : orchestratorEntry
101
+ ? [{ ...orchestratorEntry, isPersona: true }]
89
102
  : [];
90
103
 
91
104
  return entries
@@ -171,6 +184,19 @@ function checkClaude(registry, { homeDir = os.homedir() } = {}) {
171
184
 
172
185
  const OPENCODE_BUILTIN_AGENTS = new Set(['title', 'summary', 'compaction']);
173
186
 
187
+ function normalizeOpenCodeManagedAgents(actual, expected) {
188
+ const managed = actual.filter((id) => !OPENCODE_BUILTIN_AGENTS.has(id));
189
+ const expectsConstruct = expected.length === 1 && expected[0] === 'construct';
190
+ if (!expectsConstruct) return managed;
191
+ if (managed.includes('construct')) {
192
+ return managed.filter((id) => id !== 'orchestrator');
193
+ }
194
+ if (managed.length === 1 && managed[0] === 'orchestrator') {
195
+ return ['construct'];
196
+ }
197
+ return managed;
198
+ }
199
+
174
200
  function checkOpenCode(registry, { homeDir = os.homedir() } = {}) {
175
201
  const file = path.join(homeDir, '.config', 'opencode', 'opencode.json');
176
202
  if (!fs.existsSync(file)) return { surface: 'opencode', kind: 'agents', status: 'absent', file };
@@ -181,8 +207,8 @@ function checkOpenCode(registry, { homeDir = os.homedir() } = {}) {
181
207
  return { surface: 'opencode', kind: 'agents', status: 'unreadable', file, error: err.message };
182
208
  }
183
209
  const expected = entriesForSurface(registry, 'opencode');
184
- const actual = Object.keys(config.agent || config.agents || {})
185
- .filter((id) => !OPENCODE_BUILTIN_AGENTS.has(id));
210
+ const rawActual = Object.keys(config.agent || config.agents || {});
211
+ const actual = normalizeOpenCodeManagedAgents(rawActual, expected);
186
212
  const { missing, extra } = diffSets(expected, actual);
187
213
 
188
214
  // OpenCode's own system agents (title/summary/compaction) are filtered from
@@ -379,7 +405,7 @@ export function checkProjectParity({ rootDir = ROOT_DIR, projectDir = rootDir }
379
405
  // an upgrade," not real drift.
380
406
  function legacyUserScopeRoster(registry) {
381
407
  const prefix = registry.prefix || 'cx';
382
- const specialists = (registry.specialists || []).map((s) => `${prefix}-${s.name}`);
408
+ const specialists = Object.values(registry.specialists || {}).map((s) => `${prefix}-${s.name}`);
383
409
  // The front-door agent is project-scoped too now, so a leftover `construct.md`
384
410
  // in user-scope `.claude/agents` is a soft upgrade state, not real drift.
385
411
  const frontDoor = registry.orchestrator?.name ? [registry.orchestrator.name] : [];
@@ -1,37 +1,88 @@
1
1
  /**
2
2
  * lib/policy/engine.mjs — role-based policy decisions for tool / action access.
3
3
  *
4
- * Reads role permissions from `specialists/role-manifests.json` and decides
4
+ * Reads role permissions from the unified registry and decides
5
5
  * whether a given (role, project, tool, action, risk) tuple is allowed,
6
6
  * needs approval, or is denied outright. Powers the MCP broker for team
7
7
  * and enterprise deployments; solo mode leaves the broker off so this
8
8
  * engine returns allow-without-approval everywhere.
9
9
  *
10
10
  * Decision precedence:
11
- * 1. Explicit deny in the role's fence denied + typed reason.
12
- * 2. Action falls in the role's approvalRequired list allowed but
11
+ * 1. Team-level decisions gate specialist decisions forbidden by team blocks the action.
12
+ * 2. Explicit deny in the role's fencedenied + typed reason.
13
+ * 3. Action falls in the role's approvalRequired list → allowed but
13
14
  * approvalRequired = true (UI must collect human consent).
14
- * 3. risk === 'high' and role is not in HIGH_RISK_AUTONOMOUS → approval required.
15
- * 4. Otherwise → allowed.
15
+ * 4. risk === 'high' and role is not in HIGH_RISK_AUTONOMOUS → approval required.
16
+ * 5. Otherwise → allowed.
16
17
  */
17
18
 
18
19
  import { readFileSync, existsSync } from 'node:fs';
19
- import path from 'node:path';
20
- import { fileURLToPath } from 'node:url';
21
-
22
- const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
23
- const DEFAULT_MANIFEST_PATH = path.join(MODULE_DIR, '..', '..', 'specialists', 'role-manifests.json');
20
+ import { loadRegistry } from '../registry/loader.mjs';
24
21
 
25
22
  const HIGH_RISK_AUTONOMOUS = new Set(['security', 'sre', 'release-manager']);
26
23
 
27
24
  let cachedManifests = null;
28
25
 
29
- export function loadRoleManifests(manifestPath = DEFAULT_MANIFEST_PATH) {
30
- if (cachedManifests && cachedManifests.path === manifestPath) return cachedManifests.data;
31
- if (!existsSync(manifestPath)) {
32
- cachedManifests = { path: manifestPath, data: { personas: {} } };
26
+ /**
27
+ * Find the team that owns a specialist/role.
28
+ * Returns the team id if found, null otherwise.
29
+ */
30
+ function findTeamForRole(roleId, registry) {
31
+ if (!registry || !registry.teams) return null;
32
+ for (const [teamId, team] of Object.entries(registry.teams)) {
33
+ if (Array.isArray(team.roles) && team.roles.includes(roleId)) {
34
+ return teamId;
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Check if a role can make a decision at the team level.
42
+ * Returns { allowed: boolean, reason: string }.
43
+ */
44
+ function canRoleDecide(roleId, decisionId, registry) {
45
+ const teamId = findTeamForRole(roleId, registry);
46
+ if (!teamId) return { allowed: true, reason: 'no-team-restriction' };
47
+
48
+ const team = registry.teams[teamId];
49
+ if (!team) return { allowed: true, reason: 'team-not-found' };
50
+
51
+ // Check forbidden decisions first — explicit blocks override everything
52
+ if (Array.isArray(team.forbiddenDecisions) && team.forbiddenDecisions.includes(decisionId)) {
53
+ return { allowed: false, reason: 'forbidden-by-team' };
54
+ }
55
+
56
+ // Check allowed decisions — team must explicitly grant it
57
+ if (Array.isArray(team.decisionRights) && team.decisionRights.includes(decisionId)) {
58
+ return { allowed: true, reason: 'authorized-by-team' };
59
+ }
60
+
61
+ // Default: if team doesn't forbid, allow (permissive default)
62
+ return { allowed: true, reason: 'not-restricted-by-team' };
63
+ }
64
+
65
+ export function loadRoleManifests(manifestPath) {
66
+ // Unified registry is canonical. Explicit manifestPath overrides exist only for tests.
67
+ if (!manifestPath || !existsSync(manifestPath)) {
68
+ const manifestPathKey = manifestPath || '__unified_registry__';
69
+ if (cachedManifests && cachedManifests.path === manifestPathKey) return cachedManifests.data;
70
+ const registry = loadRegistry();
71
+ const personas = {};
72
+ for (const [specId, spec] of Object.entries(registry.specialists || {})) {
73
+ const personaId = specId.replace(/^cx-/, '');
74
+ const teamId = findTeamForRole(personaId, registry);
75
+ personas[personaId] = {
76
+ events: spec.events || [],
77
+ fence: spec.fence || {},
78
+ outputs: { docTypes: spec.docArtifacts || [] },
79
+ teamId: teamId || null,
80
+ };
81
+ }
82
+ cachedManifests = { path: manifestPathKey, data: { personas, registry } };
33
83
  return cachedManifests.data;
34
84
  }
85
+ if (cachedManifests && cachedManifests.path === manifestPath) return cachedManifests.data;
35
86
  const raw = readFileSync(manifestPath, 'utf8');
36
87
  const data = JSON.parse(raw);
37
88
  cachedManifests = { path: manifestPath, data };
@@ -72,12 +123,13 @@ function needsApprovalFromManifest(action, manifest) {
72
123
  * @param {string} [input.project]
73
124
  * @param {string} input.tool
74
125
  * @param {string} input.action
126
+ * @param {string} [input.decision] — optional decision id for team-level gate
75
127
  * @param {string} [input.risk]
76
128
  * @param {object} [opts]
77
129
  * @returns {{allowed: boolean, reason: string, approvalRequired: boolean, source: string}}
78
130
  */
79
131
  export function policyDecision(input = {}, opts = {}) {
80
- const { role, tool, action, risk = 'low' } = input;
132
+ const { role, tool, action, decision, risk = 'low' } = input;
81
133
  if (!role) return { allowed: false, reason: 'role is required', approvalRequired: false, source: 'engine' };
82
134
  if (!tool) return { allowed: false, reason: 'tool is required', approvalRequired: false, source: 'engine' };
83
135
  if (!action) return { allowed: false, reason: 'action is required', approvalRequired: false, source: 'engine' };
@@ -94,6 +146,19 @@ export function policyDecision(input = {}, opts = {}) {
94
146
  };
95
147
  }
96
148
 
149
+ // Team decision gate: if a decision id is supplied, check team-level authorization first
150
+ if (decision && manifests.registry) {
151
+ const teamDecision = canRoleDecide(role, decision, manifests.registry);
152
+ if (!teamDecision.allowed) {
153
+ return {
154
+ allowed: false,
155
+ reason: `decision "${decision}" forbidden for team of role "${role}": ${teamDecision.reason}`,
156
+ approvalRequired: false,
157
+ source: 'team.forbiddenDecisions',
158
+ };
159
+ }
160
+ }
161
+
97
162
  if (isExplicitlyDenied(action, manifest)) {
98
163
  return {
99
164
  allowed: false,
@@ -38,6 +38,8 @@ import { readRoleFile } from './role-preload.mjs';
38
38
  import { estimateTokens, estimatePromptTokens, estimateTokensSync } from './token-engine.js';
39
39
  import { cxDir } from './paths.mjs';
40
40
  import { getStrategyDigestSync } from './strategy-store.mjs';
41
+ import { loadRegistry } from './registry/loader.mjs';
42
+ import { bindingForSpecialist, resolveRoleOverlayId } from './roles/flavor-bindings.mjs';
41
43
 
42
44
  const MAX_OBSERVATIONS = 3;
43
45
 
@@ -46,6 +48,7 @@ const PRIORITY = {
46
48
  'core': 1,
47
49
  'task-packet': 1,
48
50
  'role-flavor': 2,
51
+ 'team-context': 2,
49
52
  'model-profile': 2,
50
53
  'context-digest': 3,
51
54
  'strategy': 3,
@@ -55,23 +58,13 @@ const PRIORITY = {
55
58
 
56
59
  // workspaceType → roleFlavors auto-selection when caller doesn't override
57
60
  const WORKSPACE_FLAVOR_MAP = {
58
- platform: { productManager: 'platform', architect: 'platform', engineer: 'platform' },
61
+ platform: { productManager: 'platform', architect: 'platform', platformEngineer: 'core' },
59
62
  enterprise: { productManager: 'enterprise', architect: 'enterprise' },
60
63
  'ai-product': { productManager: 'ai-product', architect: 'ai-systems' },
61
64
  growth: { productManager: 'growth' },
62
65
  product: {},
63
66
  };
64
67
 
65
- const AGENT_FLAVOR_MAP = {
66
- engineer: { classifierKey: 'engineer', rolePrefix: 'engineer' },
67
- architect: { classifierKey: 'architect', rolePrefix: 'architect' },
68
- 'product-manager': { classifierKey: 'productManager', rolePrefix: 'product-manager' },
69
- qa: { classifierKey: 'qa', rolePrefix: 'qa' },
70
- security: { classifierKey: 'security', rolePrefix: 'security' },
71
- 'data-analyst': { classifierKey: 'dataAnalyst', rolePrefix: 'data-analyst' },
72
- 'data-engineer': { classifierKey: 'dataEngineer', rolePrefix: 'data-engineer' },
73
- };
74
-
75
68
  // Merge workspaceType-derived overlays with explicit roleFlavors. Explicit always wins.
76
69
  function resolveRoleFlavors(roleFlavors, workspaceType, shortName) {
77
70
  const workspaceDefaults = workspaceType ? (WORKSPACE_FLAVOR_MAP[workspaceType] ?? {}) : {};
@@ -181,6 +174,14 @@ export function resolvePromptContract(entryOrName, {
181
174
  };
182
175
  }
183
176
 
177
+ if (directEntry?.promptFile) {
178
+ const body = stripSectionMarkers(readPromptBody(directEntry.promptFile, rootDir));
179
+ return {
180
+ prompt: body || fallback,
181
+ metadata: resolvePromptMetadata(directEntry.name, { rootDir, registry }),
182
+ };
183
+ }
184
+
184
185
  const agentName = directEntry?.name || entryOrName;
185
186
  if (!agentName) return { prompt: fallback, metadata: {} };
186
187
 
@@ -191,6 +192,27 @@ export function resolvePromptContract(entryOrName, {
191
192
  };
192
193
  }
193
194
 
195
+ function buildTeamBlock(entry, registry, { tokenLimit = 200, modelId = 'default' } = {}) {
196
+ if (!entry?.team || !registry?.teams) return null;
197
+ const squad = registry.teams[entry.team];
198
+ if (!squad) return null;
199
+ const groupId = squad.groupId || entry.groupId;
200
+ const group = groupId ? registry.teams[groupId] : null;
201
+ const collaborators = (squad.collaborators || []).filter(Boolean);
202
+ const lines = [
203
+ '## Team context',
204
+ '',
205
+ `Squad: **${squad.name || squad.id}** (\`${squad.id}\`)`,
206
+ ];
207
+ if (group) lines.push(`Group: **${group.name || groupId}** (\`${groupId}\`)`);
208
+ if (squad.charter) lines.push('', compactTokens(squad.charter, tokenLimit, { modelId }));
209
+ if (collaborators.length) {
210
+ lines.push('', `Collaborators: ${collaborators.map((c) => `\`${c}\``).join(', ')}`);
211
+ }
212
+ lines.push('', 'Invoke sibling squad specialists when work crosses squad boundaries. Call `suggest_skills` when the task domain is ambiguous.');
213
+ return lines.join('\n');
214
+ }
215
+
194
216
  export function composePrompt(agentName, {
195
217
  rootDir = process.cwd(),
196
218
  registry,
@@ -219,8 +241,20 @@ export function composePrompt(agentName, {
219
241
 
220
242
  fragments.push({ type: 'core', priority: PRIORITY['core'], label: entry.name, content: stripSectionMarkers(readPromptBody(entry.promptFile, rootDir)), tokenBudget: null });
221
243
 
244
+ const resolvedRegistry = registry || loadRegistry({ rootDir });
245
+ const teamBlock = buildTeamBlock(entry, resolvedRegistry, { modelId });
246
+ if (teamBlock) {
247
+ fragments.push({
248
+ type: 'team-context',
249
+ priority: PRIORITY['team-context'],
250
+ label: entry.team,
251
+ content: teamBlock,
252
+ tokenBudget: 200,
253
+ });
254
+ }
255
+
222
256
  const shortName = String(agentName).replace(/^cx-/, '');
223
- const flavorMapping = AGENT_FLAVOR_MAP[shortName];
257
+ const flavorMapping = bindingForSpecialist(shortName);
224
258
 
225
259
  // When workspaceType is provided and roleFlavors doesn't already specify this agent's flavor,
226
260
  // auto-select the matching overlay so agents get domain guidance without requiring explicit caller config.
@@ -229,7 +263,8 @@ export function composePrompt(agentName, {
229
263
  if (flavorMapping && effectiveRoleFlavors) {
230
264
  const flavor = effectiveRoleFlavors[flavorMapping.classifierKey];
231
265
  if (flavor) {
232
- const overlayBody = readRoleFile(rootDir, `${flavorMapping.rolePrefix}.${flavor}`, {
266
+ const roleId = resolveRoleOverlayId(flavorMapping, flavor);
267
+ const overlayBody = readRoleFile(rootDir, roleId, {
233
268
  source: 'prompt-composer',
234
269
  callerContext: agentName,
235
270
  });
@@ -240,8 +275,8 @@ export function composePrompt(agentName, {
240
275
  fragments.push({
241
276
  type: 'role-flavor',
242
277
  priority: PRIORITY['role-flavor'],
243
- label: `${flavorMapping.rolePrefix}.${flavor}`,
244
- content: `### ${flavor} domain guidance\n\n${overlayContent}`,
278
+ label: roleId,
279
+ content: `### ${flavor === 'core' ? shortName : flavor} domain guidance\n\n${overlayContent}`,
245
280
  tokenBudget: selectedProfile.roleFlavorTokens,
246
281
  });
247
282
  }
@@ -484,6 +519,7 @@ export function summarizePromptComposition(agentName, options = {}) {
484
519
  routeIntent: route.intent,
485
520
  routeTrack: route.track,
486
521
  routeWorkCategory: route.workCategory,
522
+ routeSuggestedWorkflowType: route.suggestedWorkflowType || null,
487
523
  routeSpecialists: route.specialists,
488
524
  routeDispatchPlan: route.dispatchPlan,
489
525
  ...(route.roleFlavors ? { routeRoleFlavors: route.roleFlavors } : {}),
@@ -6,6 +6,7 @@
6
6
  * the telemetry backend instead of embedding full production prompt text in every trace.
7
7
  */
8
8
  import crypto from 'node:crypto';
9
+ import { loadRegistry } from './registry/loader.mjs';
9
10
  import fs from 'node:fs';
10
11
  import path from 'node:path';
11
12
 
@@ -26,7 +27,7 @@ function findEntry(registry, agentName) {
26
27
  const normalized = normalizeName(agentName);
27
28
  const entries = [
28
29
  registry.orchestrator,
29
- ...(registry.specialists ?? []),
30
+ ...Object.values(registry.specialists ?? {}),
30
31
  ].filter(Boolean);
31
32
  return entries.find((entry) => {
32
33
  const entryName = normalizeName(entry.name);
@@ -36,7 +37,7 @@ function findEntry(registry, agentName) {
36
37
 
37
38
  export function resolvePromptEntry(agentName, { rootDir = process.cwd(), registry } = {}) {
38
39
  if (!agentName) return null;
39
- const loadedRegistry = registry ?? readJson(path.join(rootDir, 'specialists', 'registry.json'));
40
+ const loadedRegistry = registry ?? loadRegistry({ rootDir });
40
41
  if (!loadedRegistry) return null;
41
42
  return findEntry(loadedRegistry, agentName);
42
43
  }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * lib/providers/connection-probe.mjs — lightweight provider auth probes for doctor and tests.
3
+ *
4
+ * Issues a minimal HTTP request per API-key provider to distinguish valid keys from
5
+ * auth failures. Uses injectable fetch so functional tests stay hermetic.
6
+ */
7
+
8
+ import { resolveFirstSecret } from './secret-resolver.mjs';
9
+
10
+ const OPENROUTER_MODELS = 'https://openrouter.ai/api/v1/models';
11
+ const ANTHROPIC_MODELS = 'https://api.anthropic.com/v1/models';
12
+ const OPENAI_MODELS = 'https://api.openai.com/v1/models';
13
+
14
+ function classifyStatus(status) {
15
+ if (status === 401 || status === 403) return 'auth_error';
16
+ if (status >= 200 && status < 300) return 'ok';
17
+ if (status === 429) return 'rate_limited';
18
+ return 'probe_failed';
19
+ }
20
+
21
+ async function probeGet(url, headers, fetchImpl) {
22
+ const res = await fetchImpl(url, { method: 'GET', headers });
23
+ return { ok: classifyStatus(res.status) === 'ok', status: res.status, code: classifyStatus(res.status) };
24
+ }
25
+
26
+ export async function probeProviderConnection(providerId, { env = process.env, cwd = process.cwd(), allowAmbient = true, fetch: fetchImpl = globalThis.fetch } = {}) {
27
+ if (!fetchImpl) {
28
+ return { ok: false, code: 'fetch_unavailable', status: null, provider: providerId };
29
+ }
30
+
31
+ const secretOpts = { env, cwd, allowAmbient };
32
+
33
+ if (providerId === 'openrouter') {
34
+ const apiKey = resolveFirstSecret(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], secretOpts);
35
+ if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
36
+ return { provider: providerId, ...(await probeGet(OPENROUTER_MODELS, { Authorization: `Bearer ${apiKey}` }, fetchImpl)) };
37
+ }
38
+
39
+ if (providerId === 'anthropic') {
40
+ const apiKey = resolveFirstSecret(['ANTHROPIC_API_KEY'], secretOpts);
41
+ if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
42
+ return {
43
+ provider: providerId,
44
+ ...(await probeGet(ANTHROPIC_MODELS, {
45
+ 'x-api-key': apiKey,
46
+ 'anthropic-version': '2023-06-01',
47
+ }, fetchImpl)),
48
+ };
49
+ }
50
+
51
+ if (providerId === 'openai') {
52
+ const apiKey = resolveFirstSecret(['OPENAI_API_KEY'], secretOpts);
53
+ if (!apiKey) return { ok: false, code: 'missing_key', status: null, provider: providerId };
54
+ return { provider: providerId, ...(await probeGet(OPENAI_MODELS, { Authorization: `Bearer ${apiKey}` }, fetchImpl)) };
55
+ }
56
+
57
+ return { ok: false, code: 'unsupported_provider', status: null, provider: providerId };
58
+ }
@@ -283,7 +283,7 @@ export function __resetCopilotCache() {
283
283
  sessionCache = null;
284
284
  }
285
285
 
286
- // Exchange probe used at chat launch — a configured Copilot pin is not enough if
286
+ // Exchange probe used at host launch — a configured Copilot pin is not enough if
287
287
  // the session token cannot be minted (stale oauth, whitespace-corrupt store).
288
288
 
289
289
  export async function preflightCopilotSession({ fetchImpl = fetch } = {}) {
@@ -2,7 +2,7 @@
2
2
  * lib/providers/credential-bootstrap.mjs — lazy credential linking for Construct.
3
3
  *
4
4
  * Before any LLM surface runs, missing API keys can be satisfied from existing
5
- * stores or a one-time 1Password link into ~/.construct/config.env. Runs at
5
+ * stores or a one-time 1Password link into the XDG config.env. Runs at
6
6
  * the construct CLI entrypoint in presence-check mode; op item list runs only
7
7
  * when autoLink:true (scripts/setup-credentials.mjs).
8
8
  */
@@ -2,12 +2,11 @@
2
2
  * lib/providers/op-run.mjs — optionally launch a child under 1Password `op run`.
3
3
  *
4
4
  * When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines)
5
- * and the `op` CLI is installed, long-lived Construct services the dashboard
6
- * daemon that runs chat turns — are spawned through
5
+ * and the `op` CLI is installed, long-lived Construct services are spawned through
7
6
  * `op run --no-masking --env-file <file> -- <cmd>`, so op:// references resolve
8
7
  * once at startup into the process env. This mirrors the per-invocation model
9
- * other LLM CLIs use (e.g. `op run -- opencode`) and means a bare `construct chat`
10
- * gets resolved provider keys without a shell wrapper.
8
+ * other LLM CLIs use (e.g. `op run -- opencode`) and gives child processes
9
+ * resolved provider keys without a shell wrapper.
11
10
  *
12
11
  * Strictly opt-in: with the var unset, the file missing, or `op` absent, the
13
12
  * command is returned unchanged and 1Password is never invoked. No other setup
@@ -109,6 +109,35 @@ function readShellRcVar(varName, home) {
109
109
  return null;
110
110
  }
111
111
 
112
+ function expandHomePath(file, home) {
113
+ if (file === '~') return home;
114
+ if (file.startsWith('~/')) return `${home}${file.slice(1)}`;
115
+ return file;
116
+ }
117
+
118
+ function readOpEnvCatalogPath({ env, home }) {
119
+ const pointerSources = [
120
+ env?.CONSTRUCT_OP_ENV_FILE,
121
+ readDotenvVar(path.join(configDir(home), 'config.env'), 'CONSTRUCT_OP_ENV_FILE'),
122
+ readDotenvVar(path.join(home, '.construct', 'config.env'), 'CONSTRUCT_OP_ENV_FILE'),
123
+ ].filter((value) => typeof value === 'string' && value.length > 0);
124
+ for (const raw of pointerSources) {
125
+ const file = expandHomePath(unquote(raw), home);
126
+ try {
127
+ if (fs.existsSync(file)) return file;
128
+ } catch {
129
+ continue;
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function readFromOpEnvCatalog(varName, { env, home }) {
136
+ const catalogPath = readOpEnvCatalogPath({ env, home });
137
+ if (!catalogPath) return null;
138
+ return readDotenvVar(catalogPath, varName);
139
+ }
140
+
112
141
  function rawCandidate(varName, { env, cwd, home }) {
113
142
  const direct = env?.[varName];
114
143
  if (typeof direct === 'string' && direct.length > 0) return direct;
@@ -119,6 +148,8 @@ function rawCandidate(varName, { env, cwd, home }) {
119
148
  }
120
149
  const alt = discoverAlternateRawForVar(varName, { home });
121
150
  if (alt) return alt;
151
+ const fromOpCatalog = readFromOpEnvCatalog(varName, { env, home });
152
+ if (fromOpCatalog) return fromOpCatalog;
122
153
  return readShellRcVar(varName, home);
123
154
  }
124
155
 
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * lib/publish-tooling.mjs — detect optional binaries for the publish pipeline.
3
3
  *
4
- * Composes export (Pandoc/Typst), diagram (D2/mmdc), terminal demo (VHS), and
5
- * dashboard demo (Playwright workspace) probes for `construct tools detect`,
6
- * `construct publish --strict`, and doctor one-shot checks.
4
+ * Composes export (Pandoc/Typst), diagram (D2/mmdc), and terminal demo (VHS)
5
+ * probes for `construct tools detect`, `construct publish --strict`, and doctor
6
+ * one-shot checks.
7
7
  */
8
8
 
9
9
  import fs from 'node:fs';
@@ -13,7 +13,6 @@ import { fileURLToPath } from 'node:url';
13
13
  import { detect as detectExport } from './document-export.mjs';
14
14
  import { locateRenderer } from './diagram.mjs';
15
15
  import { locateRecorder } from './demo.mjs';
16
- import { detectDashboardDemo } from './dashboard-demo.mjs';
17
16
  import { detectIngestPipeline } from './ingest-tooling.mjs';
18
17
 
19
18
  const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
@@ -56,7 +55,6 @@ export function detectPublishPipeline({
56
55
  format = 'pdf',
57
56
  includeFigures = true,
58
57
  includeTerminalDemo = false,
59
- includeDashboardDemo = false,
60
58
  includeIngest = true,
61
59
  cwd = process.cwd(),
62
60
  repoRoot = REPO_ROOT,
@@ -89,12 +87,6 @@ export function detectPublishPipeline({
89
87
  if (!recorder) missing.push('vhs');
90
88
  }
91
89
 
92
- if (includeDashboardDemo) {
93
- const dashboard = detectDashboardDemo({ cwd, repoRoot: REPO_ROOT });
94
- steps.dashboardDemo = dashboard;
95
- if (!dashboard.present) missing.push(...dashboard.missing);
96
- }
97
-
98
90
  const uniqueMissing = [...new Set(missing)];
99
91
  return {
100
92
  ok: true,