@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
@@ -0,0 +1,125 @@
1
+ /**
2
+ * lib/config/legacy-config-migration.mjs — one-time forward migration from the
3
+ * pre-XDG ~/.construct/config.env into the active XDG config dir.
4
+ *
5
+ * Model tier overrides and credential pointers/keys stranded by the clean break
6
+ * (ADR-0045) are mirrored forward without overwriting values already in XDG.
7
+ */
8
+
9
+ import fs from 'node:fs';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+
13
+ import { parseEnvFile, writeEnvValues } from '../env-config.mjs';
14
+ import { API_KEY_CREDENTIALS } from '../providers/credential-catalog.mjs';
15
+ import { configDir } from './xdg.mjs';
16
+
17
+ const MIGRATABLE_MODEL_KEY = /^(?:CX|CONSTRUCT)_MODEL_[A-Z0-9_]+$/;
18
+ const MIGRATABLE_POINTER_KEYS = new Set(['CONSTRUCT_OP_ENV_FILE']);
19
+ const CREDENTIAL_ENV_KEYS = new Set(
20
+ API_KEY_CREDENTIALS.flatMap((entry) => entry.envVars),
21
+ );
22
+
23
+ export function legacyConfigPath(homeDir = os.homedir()) {
24
+ return path.join(homeDir, '.construct', 'config.env');
25
+ }
26
+
27
+ function xdgConfigPath(homeDir, env) {
28
+ return path.join(configDir(homeDir, env), 'config.env');
29
+ }
30
+
31
+ function expandHomePath(file, home) {
32
+ if (file === '~') return home;
33
+ if (file.startsWith('~/')) return `${home}${file.slice(1)}`;
34
+ return file;
35
+ }
36
+
37
+ function migrateKeysFromLegacy({ legacyEnv, xdgEnv, keyFilter }) {
38
+ const migrated = {};
39
+ for (const [key, value] of Object.entries(legacyEnv)) {
40
+ if (!keyFilter(key)) continue;
41
+ if (value === '' || value === undefined) continue;
42
+ if (xdgEnv[key] !== undefined && xdgEnv[key] !== '') continue;
43
+ migrated[key] = value;
44
+ }
45
+ return migrated;
46
+ }
47
+
48
+ function backfillFromOpEnvCatalog({ migrated, xdgEnv, homeDir }) {
49
+ const pointer = migrated.CONSTRUCT_OP_ENV_FILE
50
+ || xdgEnv.CONSTRUCT_OP_ENV_FILE
51
+ || legacyEnvPointer(homeDir);
52
+ if (!pointer) return migrated;
53
+
54
+ const catalogPath = expandHomePath(pointer.replace(/^["']|["']$/g, ''), homeDir);
55
+ if (!fs.existsSync(catalogPath)) return migrated;
56
+
57
+ const catalog = parseEnvFile(catalogPath);
58
+ const next = { ...migrated };
59
+ for (const key of CREDENTIAL_ENV_KEYS) {
60
+ if (xdgEnv[key] || next[key]) continue;
61
+ if (!catalog[key]) continue;
62
+ next[key] = catalog[key];
63
+ }
64
+ return next;
65
+ }
66
+
67
+ function legacyEnvPointer(homeDir) {
68
+ const legacy = parseEnvFile(legacyConfigPath(homeDir));
69
+ return legacy.CONSTRUCT_OP_ENV_FILE || null;
70
+ }
71
+
72
+ /**
73
+ * Migrate stranded CX_MODEL_ and CONSTRUCT_MODEL_ tier overrides from legacy
74
+ * config into XDG config.env.
75
+ */
76
+ export function migrateLegacyModelConfig({ homeDir = os.homedir(), env = process.env } = {}) {
77
+ const legacyPath = legacyConfigPath(homeDir);
78
+ const xdgPath = xdgConfigPath(homeDir, env);
79
+ const result = { performed: false, migrated: {}, legacyPath, xdgPath };
80
+
81
+ if (legacyPath === xdgPath || !fs.existsSync(legacyPath)) return result;
82
+
83
+ const legacyEnv = parseEnvFile(legacyPath);
84
+ const xdgEnv = parseEnvFile(xdgPath);
85
+ const migrated = migrateKeysFromLegacy({
86
+ legacyEnv,
87
+ xdgEnv,
88
+ keyFilter: (key) => MIGRATABLE_MODEL_KEY.test(key),
89
+ });
90
+
91
+ if (Object.keys(migrated).length === 0) return result;
92
+
93
+ writeEnvValues(xdgPath, migrated);
94
+ result.performed = true;
95
+ result.migrated = migrated;
96
+ return result;
97
+ }
98
+
99
+ /**
100
+ * Migrate CONSTRUCT_OP_ENV_FILE and API-key env vars from legacy config, then
101
+ * backfill any still-missing credential keys from the referenced op-run catalog.
102
+ */
103
+ export function migrateLegacyCredentialConfig({ homeDir = os.homedir(), env = process.env } = {}) {
104
+ const legacyPath = legacyConfigPath(homeDir);
105
+ const xdgPath = xdgConfigPath(homeDir, env);
106
+ const result = { performed: false, migrated: {}, legacyPath, xdgPath };
107
+
108
+ if (legacyPath === xdgPath || !fs.existsSync(legacyPath)) return result;
109
+
110
+ const legacyEnv = parseEnvFile(legacyPath);
111
+ const xdgEnv = parseEnvFile(xdgPath);
112
+ let migrated = migrateKeysFromLegacy({
113
+ legacyEnv,
114
+ xdgEnv,
115
+ keyFilter: (key) => MIGRATABLE_POINTER_KEYS.has(key) || CREDENTIAL_ENV_KEYS.has(key),
116
+ });
117
+ migrated = backfillFromOpEnvCatalog({ migrated, xdgEnv, homeDir });
118
+
119
+ if (Object.keys(migrated).length === 0) return result;
120
+
121
+ writeEnvValues(xdgPath, migrated);
122
+ result.performed = true;
123
+ result.migrated = migrated;
124
+ return result;
125
+ }
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * Schema v1 covers: alias, deployment, providers, sources, intakePolicy,
9
9
  * autoEmbed, ingest, telemetry.
10
- * Tier model selection lives in `specialists/registry.json` only — this config
10
+ * Tier model selection lives in `specialists/org` only — this config
11
11
  * file is intentionally not a second edit surface for model assignments.
12
12
  * Later phases (6, 7b) extend it with `resources` and `costs` blocks
13
13
  * without bumping the version — additive keys are allowed.
@@ -21,7 +21,7 @@ export const CONFIG_SCHEMA_VERSION = 1;
21
21
 
22
22
  export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
23
23
  export const MCP_BROKER_VALUES = ['auto', 'on', 'off'];
24
- export const DEFAULT_PROFILE_ID = 'rnd';
24
+ export const DEFAULT_SCOPE_ID = 'rnd';
25
25
 
26
26
  export const SURFACES = ['claude', 'opencode', 'codex', 'copilot', 'vscode', 'cursor'];
27
27
 
@@ -59,7 +59,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
59
59
  defaults: Object.freeze({}),
60
60
  types: Object.freeze({}),
61
61
  }),
62
- profile: DEFAULT_PROFILE_ID,
62
+ scope: DEFAULT_SCOPE_ID,
63
63
  autoEmbed: false,
64
64
  ingest: Object.freeze({
65
65
  strategy: 'adapter',
@@ -79,7 +79,7 @@ export const DEFAULT_PROJECT_CONFIG = Object.freeze({
79
79
  }),
80
80
  models: Object.freeze({
81
81
  visibility: Object.freeze({
82
- mode: 'all_configured',
82
+ mode: 'tier_defaults',
83
83
  include: [],
84
84
  exclude: [],
85
85
  providers: {},
@@ -156,7 +156,7 @@ export const FIELD_RULES = {
156
156
  types: { type: 'object', required: false },
157
157
  },
158
158
  },
159
- profile: { type: 'string', required: false, maxLength: 40 },
159
+ scope: { type: 'string', required: false, maxLength: 40 },
160
160
  autoEmbed: { type: 'boolean', required: false },
161
161
  ingest: {
162
162
  type: 'object',
@@ -309,3 +309,83 @@ export function resolveKnownSourcesFromTargets(targets) {
309
309
 
310
310
  return sources;
311
311
  }
312
+
313
+ // Team bindings take precedence over project config, which takes precedence over
314
+ // legacy env. Resolution is by typed selector signature — no substring guessing —
315
+ // so a team's effective sources are stable and conflict-free.
316
+
317
+ export function resolveTeamSources(teamId, { registry, config, env = process.env } = {}) {
318
+ const team = registry?.teams?.[teamId];
319
+ const teamTargets = Array.isArray(team?.sources)
320
+ ? team.sources.map((s) => ({
321
+ ...normalizeConfigTarget(s),
322
+ provenance: `team:${teamId}`,
323
+ ...(s.filters && typeof s.filters === 'object' ? { filters: s.filters } : {}),
324
+ }))
325
+ : [];
326
+ const configTargets = (config?.sources?.targets ?? []).map(normalizeConfigTarget);
327
+ const envTargets = legacyEnvSourceTargets(env);
328
+ return mergeSourceTargets(teamTargets, mergeSourceTargets(configTargets, envTargets));
329
+ }
330
+
331
+ // Per-target embed records that honor each target's own filters (jira jql,
332
+ // github refs/limit, slack oldest) and tag the record with the owning teamId and
333
+ // targetId so observations stay retrievable by team scope rather than a global
334
+ // pool. Falls back to the same defaults as targetsToEmbedSources when a target
335
+ // declares no filters.
336
+
337
+ export function targetsToEmbedSourcesWithFilters(targets, { teamId = null } = {}) {
338
+ const sources = [];
339
+ const tag = (rec, target) => ({
340
+ ...rec,
341
+ ...(teamId ? { teamId } : {}),
342
+ targetId: target.id,
343
+ });
344
+
345
+ for (const target of targets) {
346
+ const f = target.filters ?? {};
347
+ const sel = target.selector ?? {};
348
+ switch (target.provider) {
349
+ case 'github':
350
+ sources.push(tag({
351
+ provider: 'github',
352
+ repos: [sel.repo],
353
+ refs: Array.isArray(f.refs) ? f.refs : ['prs', 'issues', 'commits'],
354
+ limit: Number.isInteger(f.limit) ? f.limit : 25,
355
+ }, target));
356
+ break;
357
+ case 'jira':
358
+ sources.push(tag({
359
+ provider: 'jira',
360
+ project: sel.project,
361
+ refs: ['issues'],
362
+ ...(typeof f.jql === 'string' ? { jql: f.jql } : {}),
363
+ limit: Number.isInteger(f.limit) ? f.limit : 50,
364
+ }, target));
365
+ break;
366
+ case 'linear':
367
+ sources.push(tag({
368
+ provider: 'linear',
369
+ team: sel.team,
370
+ refs: ['issues'],
371
+ ...(Array.isArray(f.states) ? { states: f.states } : {}),
372
+ limit: Number.isInteger(f.limit) ? f.limit : 50,
373
+ }, target));
374
+ break;
375
+ case 'slack':
376
+ sources.push(tag({
377
+ provider: 'slack',
378
+ channels: [sel.channel],
379
+ intent: sel.intent ?? 'internal',
380
+ refs: ['messages'],
381
+ oldest: Number.isInteger(f.oldest) ? f.oldest : 86400,
382
+ limit: Number.isInteger(f.limit) ? f.limit : 50,
383
+ }, target));
384
+ break;
385
+ default:
386
+ break;
387
+ }
388
+ }
389
+
390
+ return sources;
391
+ }
@@ -1,12 +1,11 @@
1
1
  /**
2
- * lib/contracts/validate.mjs — Validate specialists/contracts.json at sync time + at handoff.
2
+ * lib/contracts/validate.mjs — Validate specialists/org contracts at sync time + at handoff.
3
3
  *
4
4
  * Three validation tiers:
5
- * 1. Schema shape: contracts.json conforms to contracts.schema.json (minimal
6
- * validator — top-level required fields, contract entry required fields).
5
+ * 1. Schema shape: unified registry conforms to unified-registry.schema.json.
7
6
  * 2. Cross-file refs: every output.schema points to a real file in lib/contract-schemas/,
8
- * every producer/consumer name resolves to an agent or persona in
9
- * specialists/registry.json, every well-known event/intake string is reachable.
7
+ * every producer/consumer name resolves to a specialist in the unified registry,
8
+ * every well-known event/intake string is reachable.
10
9
  * 3. Runtime handoff: a single artifact validated against the schema referenced
11
10
  * by a producer→consumer contract, with mustContain post-conditions.
12
11
  *
@@ -25,13 +24,11 @@ import {
25
24
  import { inferArtifactTypeFromPath } from '../artifact-type-from-path.mjs';
26
25
  import { POSTCONDITIONS, validateBinaryPostconditions } from '../specialists/postconditions.mjs';
27
26
  import { logViolation } from './violation-log.mjs';
27
+ import { loadRegistry } from '../registry/loader.mjs';
28
28
 
29
29
  const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
30
- const CONTRACTS_PATH = join(REPO_ROOT, 'specialists', 'contracts.json');
31
- const CONTRACTS_SCHEMA_PATH = join(REPO_ROOT, 'specialists', 'contracts.schema.json');
32
- const REGISTRY_PATH = join(REPO_ROOT, 'specialists', 'registry.json');
33
30
 
34
- const WELL_KNOWN_PRODUCERS = new Set(['user', 'oncall', 'incident-system', '*']);
31
+ const WELL_KNOWN_PRODUCERS = new Set(['user', 'oncall', 'incident-system', 'construct', '*']);
35
32
  const WELL_KNOWN_CONSUMERS = new Set(['user', 'construct']);
36
33
 
37
34
  function readJson(path) {
@@ -39,40 +36,50 @@ function readJson(path) {
39
36
  }
40
37
 
41
38
  /**
42
- * Validate specialists/contracts.json shape and cross-file references.
43
- * Returns { ok, errors[] } errors is empty on success.
39
+ * Validate the unified registry's contracts (shape + cross-file references).
40
+ * Reads registry.contracts via loadRegistry; an explicit contractsPath
41
+ * overrides for legacy/standalone files. Returns { ok, errors[] } — errors
42
+ * is empty on success.
44
43
  */
45
44
  export function validateContractsFile({ contractsPath, registryPath, repoRoot = REPO_ROOT } = {}) {
46
- const errors = [];
47
-
48
- // When repoRoot is supplied but explicit paths are not, derive both from it
49
- // so test fixtures and the consistency watcher can validate slices in a
50
- // tmpdir without pointing at the live repo.
51
- const cPath = contractsPath || join(repoRoot, 'specialists', 'contracts.json');
52
- const rPath = registryPath || join(repoRoot, 'specialists', 'registry.json');
53
- const schemaPath = join(repoRoot, 'specialists', 'contracts.schema.json');
54
-
55
- if (!existsSync(cPath)) {
56
- return { ok: false, errors: [`contracts file not found: ${cPath}`] };
57
- }
58
- if (!existsSync(schemaPath)) {
59
- errors.push(`contracts schema missing: ${schemaPath}`);
60
- }
61
-
62
- let contracts;
63
- try { contracts = readJson(cPath); }
64
- catch (err) { return { ok: false, errors: [`contracts.json parse error: ${err.message}`] }; }
65
-
66
- for (const key of ['version', 'terminalStates', 'severities', 'contracts']) {
67
- if (!(key in contracts)) errors.push(`contracts.json missing top-level field: ${key}`);
68
- }
69
-
70
- const knownNames = collectAgentNames(rPath);
71
-
72
- if (Array.isArray(contracts.contracts)) {
73
- const ids = new Set();
74
- contracts.contracts.forEach((c, idx) => {
75
- const where = `contracts[${idx}]${c.id ? ` (${c.id})` : ''}`;
45
+ const errors = [];
46
+
47
+ // Load contracts from unified registry or explicit path
48
+ const registry = loadRegistry();
49
+ const specialistIds = new Set(Object.keys(registry.specialists));
50
+
51
+ let contractsArray;
52
+ if (contractsPath) {
53
+ const cPath = contractsPath;
54
+ const schemaPath = join(repoRoot, 'specialists', 'contracts.schema.json');
55
+ if (!existsSync(cPath)) {
56
+ return { ok: false, errors: [`contracts file not found: ${cPath}`] };
57
+ }
58
+ if (!existsSync(schemaPath)) {
59
+ errors.push(`contracts schema missing: ${schemaPath}`);
60
+ }
61
+ const raw = readJson(cPath);
62
+ for (const key of ['version', 'terminalStates', 'severities', 'contracts']) {
63
+ if (!(key in raw)) errors.push(`contracts file missing top-level field: ${key}`);
64
+ }
65
+ contractsArray = raw.contracts || [];
66
+ } else {
67
+ // From unified registry - normalize contracts to array format
68
+ contractsArray = Object.values(registry.contracts || {});
69
+ // Support old-format contracts object with .contracts array
70
+ if (Array.isArray(contractsArray) && contractsArray.length === 0 && registry.contracts?.contracts) {
71
+ contractsArray = registry.contracts.contracts;
72
+ }
73
+ }
74
+
75
+ const knownNames = registryPath
76
+ ? collectAgentNames(registryPath)
77
+ : new Set(Object.keys(registry.specialists).concat(Object.values(registry.specialists).map(s => s.name)));
78
+
79
+ if (Array.isArray(contractsArray)) {
80
+ const ids = new Set();
81
+ contractsArray.forEach((c, idx) => {
82
+ const where = `contracts[${idx}]${c.id ? ` (${c.id})` : ''}`;
76
83
  if (!c.id) errors.push(`${where}: missing id`);
77
84
  else if (!/^[a-z0-9][a-z0-9-]*$/.test(c.id)) errors.push(`${where}: id must be kebab-case`);
78
85
  else if (ids.has(c.id)) errors.push(`${where}: duplicate id`);
@@ -124,7 +131,7 @@ function collectAgentNames(registryPath) {
124
131
  const registry = readJson(registryPath);
125
132
  if (registry.orchestrator?.name) names.add(registry.orchestrator.name);
126
133
  if (registry.orchestrator?.displayName) names.add(registry.orchestrator.displayName);
127
- for (const s of registry.specialists || []) {
134
+ for (const s of Object.values(registry.specialists || {})) {
128
135
  if (s?.name) names.add(s.name);
129
136
  if (s?.displayName) names.add(s.displayName);
130
137
  }
@@ -135,14 +142,21 @@ function collectAgentNames(registryPath) {
135
142
  /**
136
143
  * Look up a contract by producer/consumer pair (and optional id).
137
144
  */
138
- export function findContract({ producer, consumer, id, contractsPath = CONTRACTS_PATH }) {
139
- if (!existsSync(contractsPath)) return null;
140
- let contracts;
141
- try { contracts = readJson(contractsPath); } catch { return null; }
142
- const list = contracts.contracts || [];
143
- if (id) return list.find((c) => c.id === id) || null;
144
- return list.find((c) => c.producer === producer && c.consumer === consumer) || null;
145
- }
145
+ export function findContract({ producer, consumer, id, contractsPath }) {
146
+ if (contractsPath) {
147
+ if (!existsSync(contractsPath)) return null;
148
+ try {
149
+ const contracts = readJson(contractsPath);
150
+ const list = contracts.contracts || [];
151
+ if (id) return list.find((c) => c.id === id) || null;
152
+ return list.find((c) => c.producer === producer && c.consumer === consumer) || null;
153
+ } catch { return null; }
154
+ }
155
+ // Default: use unified registry
156
+ const registry = loadRegistry();
157
+ if (id) return registry.contracts[id] || null;
158
+ return Object.values(registry.contracts).find((c) => c.producer === producer && c.consumer === consumer) || null;
159
+ }
146
160
 
147
161
  /**
148
162
  * Validate a single artifact against its contract at handoff time.
@@ -168,7 +182,7 @@ export function validateHandoff(opts = {}) {
168
182
  id,
169
183
  artifact,
170
184
  packet,
171
- contractsPath = CONTRACTS_PATH,
185
+ contractsPath,
172
186
  repoRoot = REPO_ROOT,
173
187
  enforcement = 'block',
174
188
  } = opts;
@@ -216,6 +230,14 @@ export function validateHandoff(opts = {}) {
216
230
  : { ok: true, warnings: [`no contract found for ${producer}→${consumer}`], contract: null };
217
231
  }
218
232
 
233
+ // Team boundary validation: check if cross-team handoff requires approvals.
234
+ if (contract.teamBoundary?.crosses && Array.isArray(contract.teamBoundary.requiresApprovalFrom)) {
235
+ const teamBoundaryErrors = validateTeamBoundary(contract);
236
+ for (const err of teamBoundaryErrors) {
237
+ errors.push(err);
238
+ }
239
+ }
240
+
219
241
  // A handoff carries a producer's output, which is the consumer's input. The
220
242
  // input contract is what the consumer expects to receive; check mustContain
221
243
  // against that first. Output.schema validation only applies when the artifact
@@ -282,6 +304,52 @@ function hasField(obj, field) {
282
304
  return Object.prototype.hasOwnProperty.call(obj, field) && obj[field] != null && obj[field] !== '';
283
305
  }
284
306
 
307
+ /**
308
+ * Validate team boundary enforcement on a contract.
309
+ * Returns array of error strings (empty if validation passes).
310
+ *
311
+ * Team boundaries are declarative metadata that enforce organizational
312
+ * separation of concerns. A contract.teamBoundary.requiresApprovalFrom
313
+ * lists teams that must explicitly approve before the handoff can complete.
314
+ */
315
+ function validateTeamBoundary(contract = {}) {
316
+ const errors = [];
317
+ const boundary = contract.teamBoundary || {};
318
+
319
+ if (!boundary.crosses) return errors;
320
+ if (!Array.isArray(boundary.requiresApprovalFrom) || boundary.requiresApprovalFrom.length === 0) {
321
+ return errors;
322
+ }
323
+
324
+ // Check that producer and consumer teams are declared.
325
+ if (!boundary.producerTeam) {
326
+ errors.push(`team boundary marked as crossing but producerTeam is missing in contract ${contract.id}`);
327
+ }
328
+ if (!boundary.consumerTeam) {
329
+ errors.push(`team boundary marked as crossing but consumerTeam is missing in contract ${contract.id}`);
330
+ }
331
+
332
+ // Load registry to verify team ids exist.
333
+ try {
334
+ const registry = loadRegistry();
335
+ for (const teamId of boundary.requiresApprovalFrom) {
336
+ if (!registry.teams?.[teamId]) {
337
+ errors.push(`team boundary references unknown team: ${teamId} (in contract ${contract.id})`);
338
+ }
339
+ }
340
+ if (boundary.producerTeam && !registry.teams?.[boundary.producerTeam]) {
341
+ errors.push(`contract ${contract.id}: producerTeam '${boundary.producerTeam}' not found in registry`);
342
+ }
343
+ if (boundary.consumerTeam && !registry.teams?.[boundary.consumerTeam]) {
344
+ errors.push(`contract ${contract.id}: consumerTeam '${boundary.consumerTeam}' not found in registry`);
345
+ }
346
+ } catch (err) {
347
+ errors.push(`failed to validate team boundary for contract ${contract.id}: ${err.message}`);
348
+ }
349
+
350
+ return errors;
351
+ }
352
+
285
353
  const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
286
354
 
287
355
  function parseArtifactFrontmatter(text) {
@@ -1,3 +1,4 @@
1
+ import { loadRegistry } from '../registry/loader.mjs';
1
2
  /**
2
3
  * lib/decisions/golden.mjs — behavioral golden snapshot of core surfaces.
3
4
  *
@@ -38,8 +39,8 @@ export function buildSurfaceSnapshot({ repoRoot = REPO_ROOT } = {}) {
38
39
  }
39
40
  snapshot.commands.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
40
41
 
41
- const registry = JSON.parse(readFileSync(join(repoRoot, 'specialists', 'registry.json'), 'utf8'));
42
- snapshot.agents = (registry.specialists || []).map((s) => s.name).sort();
42
+ const registry = loadRegistry({ rootDir: repoRoot });
43
+ snapshot.agents = Object.values(registry.specialists || {}).map((s) => s.name).sort();
43
44
 
44
45
  const settings = JSON.parse(readFileSync(join(repoRoot, 'platforms', 'claude', 'settings.template.json'), 'utf8'));
45
46
  for (const event of HOOK_EVENTS) {
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * Three decision sources are indexed:
10
10
  * - ADRs docs/decisions/adr/*.md (status + supersedes parsed from body)
11
- * - Contracts specialists/contracts.json (executable postconditions = enforcement)
11
+ * - Contracts specialists/org (executable postconditions = enforcement)
12
12
  * - Rules rules/**\/*.md (enforcement link via frontmatter; bead wvbf.4)
13
13
  *
14
14
  * Enforcement bindings are discovered by scanning tests/ and lib/hooks/ for an
@@ -24,6 +24,7 @@ import { join, dirname, resolve, relative } from 'node:path';
24
24
  import { fileURLToPath } from 'node:url';
25
25
 
26
26
  import { readRuleFile } from '../rules-read.mjs';
27
+ import { loadRegistry } from '../registry/loader.mjs';
27
28
 
28
29
  export const DECISION_STATUSES = ['proposed', 'accepted', 'superseded', 'deprecated'];
29
30
 
@@ -152,11 +153,15 @@ function adrDecisions({ repoRoot, bindings }) {
152
153
  }
153
154
 
154
155
  function contractDecisions({ repoRoot, bindings }) {
155
- const file = join(repoRoot, 'specialists', 'contracts.json');
156
- if (!existsSync(file)) return [];
157
- const parsed = JSON.parse(readFileSync(file, 'utf8'));
156
+ const registryFile = join('specialists', 'org');
157
+ let contractsArray;
158
+ try {
159
+ contractsArray = Object.values(loadRegistry({ rootDir: repoRoot, skipValidation: true }).contracts || {});
160
+ } catch {
161
+ return [];
162
+ }
158
163
  const out = [];
159
- for (const c of parsed.contracts || []) {
164
+ for (const c of contractsArray) {
160
165
  const checks = [];
161
166
  let advisoryPostconditions = 0;
162
167
  for (const pc of c.postconditions || []) {
@@ -171,7 +176,7 @@ function contractDecisions({ repoRoot, bindings }) {
171
176
  status: 'accepted',
172
177
  supersedes: null,
173
178
  supersededBy: null,
174
- file: relative(repoRoot, file),
179
+ file: registryFile,
175
180
  lastVerifiedAt: null,
176
181
  enforcingTests,
177
182
  enforcingChecks: checks,