@geraldmaron/construct 1.2.3 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (505) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +235 -209
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +55 -54
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +90 -24
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/cli.mjs +12 -2
  44. package/lib/doctor/source-checkout.mjs +3 -4
  45. package/lib/doctor/watchers/consistency.mjs +148 -43
  46. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  47. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  48. package/lib/doctor/watchers/service-health.mjs +4 -32
  49. package/lib/document-export.mjs +73 -9
  50. package/lib/document-extract.mjs +10 -198
  51. package/lib/embed/artifact.mjs +2 -3
  52. package/lib/embed/cli.mjs +1 -1
  53. package/lib/embed/config.mjs +1 -2
  54. package/lib/embed/customer-profiles.mjs +1 -1
  55. package/lib/embed/daemon.mjs +5 -6
  56. package/lib/embed/demand-fetch.mjs +114 -2
  57. package/lib/embed/docs-lifecycle.mjs +9 -5
  58. package/lib/embed/intake-metrics.mjs +2 -2
  59. package/lib/embed/notifications.mjs +2 -3
  60. package/lib/embed/recommendation-store.mjs +25 -22
  61. package/lib/embed/role-framing.mjs +4 -9
  62. package/lib/embedded-contract/capability.mjs +4 -2
  63. package/lib/embedded-contract/contract-version.mjs +1 -1
  64. package/lib/env-config.mjs +2 -2
  65. package/lib/flavors/loader.mjs +21 -19
  66. package/lib/graph/build-from-registry.mjs +31 -7
  67. package/lib/graph/staleness.mjs +1 -1
  68. package/lib/headhunt.mjs +82 -35
  69. package/lib/hooks/agent-tracker.mjs +2 -2
  70. package/lib/hooks/config-protection.mjs +1 -1
  71. package/lib/hooks/registry-sync.mjs +3 -3
  72. package/lib/host-capabilities.mjs +5 -5
  73. package/lib/improvement/controller.mjs +2 -2
  74. package/lib/init-unified.mjs +19 -21
  75. package/lib/intake/classify.mjs +26 -0
  76. package/lib/intake/daemon.mjs +2 -3
  77. package/lib/intake/prepare.mjs +10 -3
  78. package/lib/intake/session-prelude.mjs +1 -1
  79. package/lib/intake/tables/creative.mjs +9 -9
  80. package/lib/intake/tables/operations.mjs +4 -4
  81. package/lib/intake/tables/research.mjs +2 -2
  82. package/lib/integrations/intake-integrations.mjs +9 -10
  83. package/lib/knowledge/research-store.mjs +2 -2
  84. package/lib/mcp/server.mjs +104 -61
  85. package/lib/mcp/tool-budget.mjs +1 -46
  86. package/lib/mcp/tool-recovery.mjs +44 -0
  87. package/lib/mcp/tools/artifact-author.mjs +36 -0
  88. package/lib/mcp/tools/find-tool.mjs +86 -0
  89. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  90. package/lib/mcp/tools/project.mjs +4 -3
  91. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  92. package/lib/mcp/tools/skills.mjs +39 -22
  93. package/lib/mcp/tools/telemetry.mjs +2 -1
  94. package/lib/mcp/tools/workflow.mjs +1 -1
  95. package/lib/mcp-platform-config.mjs +7 -5
  96. package/lib/migrations/index.mjs +4 -2
  97. package/lib/migrations/v2-unified-registry.mjs +35 -0
  98. package/lib/model-router.mjs +203 -36
  99. package/lib/models/catalog.mjs +25 -9
  100. package/lib/models/execution-capability-profile.mjs +3 -3
  101. package/lib/models/execution-policy.mjs +7 -6
  102. package/lib/models/provider-poll.mjs +18 -4
  103. package/lib/opencode-config.mjs +56 -1
  104. package/lib/opencode-runtime-plugin.mjs +13 -10
  105. package/lib/oracle/artifact-gate.mjs +14 -4
  106. package/lib/oracle/cli.mjs +2 -0
  107. package/lib/oracle/dispatch.mjs +18 -1
  108. package/lib/oracle/execute.mjs +39 -7
  109. package/lib/oracle/index.mjs +1 -1
  110. package/lib/oracle/org-graph.mjs +10 -5
  111. package/lib/oracle/policy.mjs +6 -0
  112. package/lib/oracle/read-model.mjs +103 -6
  113. package/lib/oracle/reconcile.mjs +31 -4
  114. package/lib/oracle/remediation-dispatch.mjs +53 -0
  115. package/lib/oracle/routing.mjs +5 -0
  116. package/lib/oracle/synthesize.mjs +80 -3
  117. package/lib/orchestration/routing-tables.mjs +44 -10
  118. package/lib/orchestration/runtime.mjs +2 -0
  119. package/lib/orchestration/worker.mjs +1 -1
  120. package/lib/orchestration-policy.mjs +351 -62
  121. package/lib/overrides/resolver.mjs +1 -12
  122. package/lib/parity.mjs +37 -11
  123. package/lib/policy/engine.mjs +80 -15
  124. package/lib/prompt-composer.js +51 -15
  125. package/lib/prompt-metadata.mjs +3 -2
  126. package/lib/providers/connection-probe.mjs +58 -0
  127. package/lib/providers/copilot-auth.mjs +1 -1
  128. package/lib/providers/credential-bootstrap.mjs +1 -1
  129. package/lib/providers/op-run.mjs +3 -4
  130. package/lib/providers/secret-resolver.mjs +31 -0
  131. package/lib/publish-tooling.mjs +3 -11
  132. package/lib/publish.mjs +12 -29
  133. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  134. package/lib/reflect.mjs +2 -2
  135. package/lib/registry/agent-manifest.mjs +190 -0
  136. package/lib/registry/assemble.mjs +133 -0
  137. package/lib/registry/catalog.mjs +171 -0
  138. package/lib/registry/cli.mjs +590 -6
  139. package/lib/registry/consolidation.mjs +5 -4
  140. package/lib/registry/docs-sync.mjs +67 -0
  141. package/lib/registry/loader.mjs +147 -0
  142. package/lib/registry/org-io.mjs +76 -0
  143. package/lib/registry/retired-paths.mjs +71 -0
  144. package/lib/registry/surface-map.mjs +5 -7
  145. package/lib/registry/validator.mjs +438 -0
  146. package/lib/research-execution-policy.mjs +173 -0
  147. package/lib/roles/catalog.mjs +4 -9
  148. package/lib/roles/fence.mjs +107 -1
  149. package/lib/roles/flavor-bindings.mjs +68 -0
  150. package/lib/roles/gateway.mjs +140 -29
  151. package/lib/roles/manifest.mjs +22 -13
  152. package/lib/roles/router.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +6 -26
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -8
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +269 -51
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  433. package/apps/chat/engine/loop-driver.mjs +0 -211
  434. package/apps/chat/engine/models.mjs +0 -122
  435. package/apps/chat/engine/provider-adapters.mjs +0 -171
  436. package/apps/chat/engine/tools/permission.mjs +0 -54
  437. package/apps/chat/engine/tools/primitives.mjs +0 -180
  438. package/apps/chat/engine/tools/registry.mjs +0 -122
  439. package/apps/chat/engine/turn-controls.mjs +0 -70
  440. package/lib/boundary.mjs +0 -127
  441. package/lib/certification/dashboard-api.mjs +0 -71
  442. package/lib/chat/cli.mjs +0 -333
  443. package/lib/chat/command-suggest.mjs +0 -161
  444. package/lib/chat/commands.mjs +0 -215
  445. package/lib/chat/config.mjs +0 -142
  446. package/lib/chat/context-compactor.mjs +0 -250
  447. package/lib/chat/context-continuation.mjs +0 -253
  448. package/lib/chat/continuation-source.mjs +0 -58
  449. package/lib/chat/demo-guide.mjs +0 -61
  450. package/lib/chat/design-tokens.mjs +0 -91
  451. package/lib/chat/desktop-binary.mjs +0 -81
  452. package/lib/chat/desktop-build.mjs +0 -130
  453. package/lib/chat/desktop-launcher.mjs +0 -133
  454. package/lib/chat/evidence.mjs +0 -145
  455. package/lib/chat/export.mjs +0 -74
  456. package/lib/chat/harness/driver.mjs +0 -91
  457. package/lib/chat/list-picker.mjs +0 -112
  458. package/lib/chat/model-picker.mjs +0 -356
  459. package/lib/chat/openrouter-fallback.mjs +0 -151
  460. package/lib/chat/permission-prompt.mjs +0 -33
  461. package/lib/chat/picker-catalog.mjs +0 -45
  462. package/lib/chat/policy-telemetry.mjs +0 -34
  463. package/lib/chat/present.mjs +0 -246
  464. package/lib/chat/session-context.mjs +0 -39
  465. package/lib/chat/session-persist.mjs +0 -73
  466. package/lib/chat/session-restore.mjs +0 -71
  467. package/lib/chat/session-settings.mjs +0 -53
  468. package/lib/chat/system-prompt.mjs +0 -52
  469. package/lib/chat/transparency.mjs +0 -93
  470. package/lib/chat/tui/color-scheme.mjs +0 -42
  471. package/lib/chat/tui/markdown.mjs +0 -123
  472. package/lib/chat/tui/presentation.mjs +0 -100
  473. package/lib/chat/tui/render.mjs +0 -500
  474. package/lib/chat/tui/turn-block.mjs +0 -284
  475. package/lib/chat/tui/turn-present.mjs +0 -18
  476. package/lib/chat/tui/turn-state.mjs +0 -88
  477. package/lib/chat/tui/usage.mjs +0 -122
  478. package/lib/chat/web-commands.mjs +0 -146
  479. package/lib/chat/web-launcher.mjs +0 -63
  480. package/lib/chat/web-picker-keys.mjs +0 -46
  481. package/lib/chat/web-session.mjs +0 -159
  482. package/lib/config/alias.mjs +0 -57
  483. package/lib/dashboard-demo.mjs +0 -71
  484. package/lib/dashboard-static.mjs +0 -175
  485. package/lib/install/desktop-binary-download.mjs +0 -88
  486. package/lib/profiles/loader.mjs +0 -123
  487. package/lib/profiles/rebrand.mjs +0 -46
  488. package/lib/server/auth.mjs +0 -169
  489. package/lib/server/chat-loop.mjs +0 -622
  490. package/lib/server/chat.mjs +0 -336
  491. package/lib/server/cors.mjs +0 -77
  492. package/lib/server/csrf.mjs +0 -103
  493. package/lib/server/demo-preview.mjs +0 -63
  494. package/lib/server/index.mjs +0 -2641
  495. package/lib/server/insights.mjs +0 -780
  496. package/lib/server/langfuse-login.mjs +0 -58
  497. package/lib/server/rate-limit.mjs +0 -93
  498. package/lib/server/telemetry-login.mjs +0 -100
  499. package/lib/server/webhook.mjs +0 -512
  500. package/specialists/contracts.json +0 -1032
  501. package/specialists/contracts.schema.json +0 -83
  502. package/specialists/policy-inventory.json +0 -188
  503. package/specialists/registry.json +0 -1412
  504. package/specialists/role-manifests.json +0 -217
  505. package/specialists/teams.json +0 -94
@@ -24,8 +24,9 @@ import { cxDir } from '../paths.mjs';
24
24
  const DEFAULT_SUPPRESS_DAYS = 7;
25
25
  const SUPERSEDE_WITHIN_HOURS = 72;
26
26
 
27
- function recommendationPaths() {
28
- const storeDir = join(cxDir(), 'intake');
27
+ function recommendationPaths({ rootDir, project } = {}) {
28
+ const baseDir = rootDir || project;
29
+ const storeDir = baseDir ? join(baseDir, '.cx', 'intake') : join(cxDir(), 'intake');
29
30
  return {
30
31
  storeDir,
31
32
  logFile: join(storeDir, 'recommendations.jsonl'),
@@ -36,16 +37,16 @@ function recommendationPaths() {
36
37
  /**
37
38
  * Ensure store directory exists.
38
39
  */
39
- function ensureDir() {
40
- const { storeDir } = recommendationPaths();
40
+ function ensureDir(opts = {}) {
41
+ const { storeDir } = recommendationPaths(opts);
41
42
  if (!existsSync(storeDir)) mkdirSync(storeDir, { recursive: true });
42
43
  }
43
44
 
44
45
  /**
45
46
  * Read the index (fast lookup).
46
47
  */
47
- function readIndex() {
48
- const { indexFile } = recommendationPaths();
48
+ function readIndex(opts = {}) {
49
+ const { indexFile } = recommendationPaths(opts);
49
50
  if (!existsSync(indexFile)) return {};
50
51
  try {
51
52
  return JSON.parse(readFileSync(indexFile, 'utf8'));
@@ -57,9 +58,9 @@ function readIndex() {
57
58
  /**
58
59
  * Write the index.
59
60
  */
60
- function writeIndex(index) {
61
- const { indexFile } = recommendationPaths();
62
- ensureDir();
61
+ function writeIndex(index, opts = {}) {
62
+ const { indexFile } = recommendationPaths(opts);
63
+ ensureDir(opts);
63
64
  writeFileSync(indexFile, JSON.stringify(index, null, 2) + '\n');
64
65
  }
65
66
 
@@ -86,9 +87,10 @@ function dedupKey(type, title) {
86
87
  export function createRecommendation({ type, title, reason, signalCount = 1, customerImpact = 0, recencyBonus = 0, strategicBonus = 0, sourceSignalIds = [], lane, project, env }) {
87
88
  if (!type || !title) throw new Error('type and title are required');
88
89
 
89
- ensureDir();
90
+ const pathOpts = { rootDir: project };
91
+ ensureDir(pathOpts);
90
92
  const key = dedupKey(type, title);
91
- const index = readIndex();
93
+ const index = readIndex(pathOpts);
92
94
 
93
95
  if (index[key]) {
94
96
  const existing = index[key];
@@ -106,7 +108,7 @@ export function createRecommendation({ type, title, reason, signalCount = 1, cus
106
108
  updated.score = score;
107
109
  updated.priority = priorityTier(score);
108
110
  index[key] = updated;
109
- writeIndex(index);
111
+ writeIndex(index, pathOpts);
110
112
 
111
113
  return { id: existing.id, dedupKey: key, priority: updated.priority, score, existing: true, updatedAt: updated.lastSeen };
112
114
  }
@@ -139,10 +141,10 @@ export function createRecommendation({ type, title, reason, signalCount = 1, cus
139
141
  enrichedBy: null,
140
142
  };
141
143
 
142
- appendFileSync(recommendationPaths().logFile, JSON.stringify(rec) + '\n', 'utf8');
144
+ appendFileSync(recommendationPaths(pathOpts).logFile, JSON.stringify(rec) + '\n', 'utf8');
143
145
 
144
146
  index[key] = rec;
145
- writeIndex(index);
147
+ writeIndex(index, pathOpts);
146
148
 
147
149
  emitBestEffort('recommendation.generated', {
148
150
  project: project || '',
@@ -202,7 +204,7 @@ export function supersedeRecommendation(dedupKey, supersedingId) {
202
204
  * List active recommendations (filesystem-only).
203
205
  */
204
206
  export function listActiveRecommendations({ type, priority, limit = 20, project, env } = {}) {
205
- const index = readIndex();
207
+ const index = readIndex({ rootDir: project });
206
208
  const now = new Date();
207
209
  let results = Object.values(index).filter(rec => {
208
210
  if (rec.dismissedAt) {
@@ -222,8 +224,9 @@ export function listActiveRecommendations({ type, priority, limit = 20, project,
222
224
  .slice(0, limit);
223
225
  }
224
226
 
225
- export function autoSuppressStale() {
226
- const index = readIndex();
227
+ export function autoSuppressStale({ project } = {}) {
228
+ const pathOpts = { rootDir: project };
229
+ const index = readIndex(pathOpts);
227
230
  const now = Date.now();
228
231
  let suppressed = 0;
229
232
 
@@ -255,7 +258,7 @@ export function autoSuppressStale() {
255
258
  }
256
259
  }
257
260
 
258
- if (suppressed) writeIndex(index);
261
+ if (suppressed) writeIndex(index, pathOpts);
259
262
  return suppressed;
260
263
  }
261
264
 
@@ -291,8 +294,8 @@ export function reviveRecommendation(dedupKey, { signalCount = 1, sourceSignalId
291
294
  return { success: true };
292
295
  }
293
296
 
294
- export function recommendationStats() {
295
- const index = readIndex();
297
+ export function recommendationStats({ project } = {}) {
298
+ const index = readIndex({ rootDir: project });
296
299
  const entries = Object.values(index);
297
300
  const byPriority = {};
298
301
  let active = 0, dismissed = 0, superseded = 0;
@@ -314,9 +317,9 @@ export function recommendationStats() {
314
317
  };
315
318
  }
316
319
 
317
- export function isRecommendationActive(type, title) {
320
+ export function isRecommendationActive(type, title, { project } = {}) {
318
321
  const key = dedupKey(type, title);
319
- const index = readIndex();
322
+ const index = readIndex({ rootDir: project });
320
323
  const rec = index[key];
321
324
  if (!rec) return { active: false, existing: null };
322
325
  if (rec.dismissedAt || rec.supersededAt) return { active: false, existing: rec };
@@ -7,22 +7,17 @@
7
7
  * 3. Renders a "Role Lens" section for inclusion in snapshots/roadmaps
8
8
  */
9
9
 
10
- import { readFileSync } from 'node:fs';
11
- import { join, dirname } from 'node:path';
12
- import { fileURLToPath } from 'node:url';
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
- const REGISTRY_PATH = join(__dirname, '..', '..', 'specialists', 'registry.json');
10
+ import { loadRegistry as loadAssembledRegistry } from '../registry/loader.mjs';
16
11
 
17
12
  let _registryCache = null;
18
13
 
19
14
  function loadRegistry() {
20
15
  if (_registryCache) return _registryCache;
21
16
  try {
22
- _registryCache = JSON.parse(readFileSync(REGISTRY_PATH, 'utf8'));
17
+ _registryCache = loadAssembledRegistry();
23
18
  } catch (err) {
24
19
  process.stderr.write('[role-framing.mjs] loadRegistry: ' + (err?.message ?? String(err)) + '\n');
25
- _registryCache = { specialists: [] };
20
+ _registryCache = { specialists: {} };
26
21
  }
27
22
  return _registryCache;
28
23
  }
@@ -35,7 +30,7 @@ function loadRegistry() {
35
30
  export function getOrientation(roleName) {
36
31
  if (!roleName) return null;
37
32
  const registry = loadRegistry();
38
- const agent = (registry.specialists ?? []).find((a) => a.name === roleName);
33
+ const agent = Object.values(registry.specialists ?? {}).find((a) => a.name === roleName);
39
34
  return agent?.embedOrientation ?? null;
40
35
  }
41
36
 
@@ -23,6 +23,7 @@ import { resolveModelTiers } from '../model-registry.mjs';
23
23
  import { readSkillFrontmatter } from '../sync/skill-frontmatter.mjs';
24
24
  import { loadPluginRegistry } from '../plugin-registry.mjs';
25
25
  import { listWorkflowDefs } from './workflow-defs.mjs';
26
+ import { loadRegistry } from '../registry/loader.mjs';
26
27
  import { APPROVAL_MODES } from './audit.mjs';
27
28
 
28
29
  const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
@@ -111,8 +112,9 @@ function buildModels(env) {
111
112
 
112
113
  function buildPolicies(rootDir, warnings) {
113
114
  try {
114
- const raw = JSON.parse(readFileSync(join(rootDir, 'specialists', 'policy-inventory.json'), 'utf8'));
115
- const arr = Array.isArray(raw) ? raw : (raw.policies || Object.values(raw));
115
+ const registry = loadRegistry({ rootDir, skipValidation: true });
116
+ const policies = registry.policies || {};
117
+ const arr = Array.isArray(policies) ? policies : Object.values(policies);
116
118
  return arr.map((p) => ({ id: p.id, enforcement: p.enforcement, mode: p.mode, description: p.description }));
117
119
  } catch (err) {
118
120
  warnings.push(`Policy inventory unavailable: ${err.message}`);
@@ -8,7 +8,7 @@
8
8
  * CONTRACT_VERSION so an embedding application can negotiate compatibility
9
9
  * without reading internal registries.
10
10
  *
11
- * Distinct from specialists/contracts.json `version` (handoff contracts) and
11
+ * Distinct from specialists/org `version` (handoff contracts) and
12
12
  * lib/plugin-registry.mjs `MANIFEST_VERSION` (plugin manifests); those version
13
13
  * unrelated internal surfaces.
14
14
  */
@@ -88,7 +88,7 @@ const ENV_SHADOW_IGNORE = new Set([
88
88
  */
89
89
  const ENV_SHADOW_WARN = new Set([
90
90
  'OPENROUTER_API_KEY', 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY',
91
- 'DASHBOARD_PORT', 'MEMORY_PORT', 'BRIDGE_PORT',
91
+ 'MEMORY_PORT', 'BRIDGE_PORT',
92
92
  'CONSTRUCT_INSTANCE_ID',
93
93
  'CONSTRUCT_DEPLOYMENT_MODE',
94
94
  ]);
@@ -155,7 +155,7 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
155
155
  }
156
156
 
157
157
  // process.env is the base; .env-file values override shell values so that
158
- // credentials set in the dashboard always win over stale shell exports.
158
+ // credentials set in config.env always win over stale shell exports.
159
159
  // Materialized credentials from `op run` (or an explicit export) win over stored
160
160
  // op:// references so one 1Password auth per invocation is enough.
161
161
  const merged = { ...env, ...fileEnv };
@@ -2,12 +2,12 @@
2
2
  * lib/flavors/loader.mjs — Role flavor overlay loader and validator.
3
3
  *
4
4
  * Reads frontmatter from skills/roles/<role>.<flavor>.md files, exposes:
5
- * - listFlavors(role?, profile?) — overlays that apply to role and profile
5
+ * - listFlavors(role?, scope?) — overlays that apply to role and scope
6
6
  * - validateFlavor(path) — frontmatter conformance check against the schema
7
- * - perRoleFlavorCount(profile) — flavor counts per role for cap enforcement
7
+ * - perRoleFlavorCount(scope) — flavor counts per role for cap enforcement
8
8
  *
9
- * The cap is enforced at sync time and in lint-profiles.
10
- * Six flavors per role per profile is the current ceiling; bumping requires
9
+ * The cap is enforced at sync time and in lint-scopes.
10
+ * Six flavors per role per scope is the current ceiling; bumping requires
11
11
  * an ADR. Existing roles like qa already have 6, so the cap is set tight.
12
12
  */
13
13
  import fs from 'node:fs';
@@ -18,7 +18,8 @@ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
18
18
  const REPO_ROOT = path.resolve(MODULE_DIR, '..', '..');
19
19
  const FLAVORS_DIR = path.join(REPO_ROOT, 'skills', 'roles');
20
20
 
21
- export const FLAVOR_CAP_PER_ROLE_PER_PROFILE = 6;
21
+ export const FLAVOR_CAP_PER_ROLE_PER_SCOPE = 6;
22
+ export const FLAVOR_CAP_PER_ROLE_PER_PROFILE = FLAVOR_CAP_PER_ROLE_PER_SCOPE;
22
23
 
23
24
  // Files may start with an HTML comment block before the YAML frontmatter.
24
25
  // The /m flag lets ^ match line-start so we find the YAML wherever it sits.
@@ -79,30 +80,26 @@ export function listAllFlavors() {
79
80
  * Return overlays that match the given role (any-flavor for that role) and
80
81
  * apply to the given profile. Pass undefined for either to skip that filter.
81
82
  */
82
- export function listFlavors({ role, profile } = {}) {
83
+ export function listFlavors({ role, scope, profile } = {}) {
84
+ const scopeId = scope ?? profile;
83
85
  return listAllFlavors().filter(({ frontmatter }) => {
84
86
  if (role) {
85
87
  const roleId = frontmatter.role || '';
86
88
  const baseRole = roleId.split('.')[0];
87
89
  if (baseRole !== role && roleId !== role) return false;
88
90
  }
89
- if (profile) {
90
- const profiles = frontmatter.profiles;
91
- if (!Array.isArray(profiles) || profiles.length === 0) return true;
92
- if (!profiles.includes(profile)) return false;
91
+ if (scopeId) {
92
+ const scopes = frontmatter.scopes ?? frontmatter.profiles;
93
+ if (!Array.isArray(scopes) || scopes.length === 0) return true;
94
+ if (!scopes.includes(scopeId)) return false;
93
95
  }
94
96
  return true;
95
97
  });
96
98
  }
97
99
 
98
- /**
99
- * Count flavor overlays per base role for a given profile.
100
- * Returns { [baseRole]: count }. Excludes the canonical base file (e.g.
101
- * architect.md) so only true flavors count toward the cap.
102
- */
103
- export function perRoleFlavorCount(profile = 'rnd') {
100
+ export function perRoleFlavorCount(scope = 'rnd') {
104
101
  const counts = {};
105
- for (const entry of listFlavors({ profile })) {
102
+ for (const entry of listFlavors({ scope })) {
106
103
  const roleId = entry.frontmatter.role || '';
107
104
  const isFlavor = roleId.includes('.');
108
105
  if (!isFlavor) continue;
@@ -129,8 +126,13 @@ export function validateFlavor(filePath) {
129
126
  if (!Array.isArray(fm.applies_to) || fm.applies_to.length === 0) {
130
127
  errors.push(`${filePath}: applies_to must be a non-empty array`);
131
128
  }
132
- if (!Array.isArray(fm.profiles) || fm.profiles.length === 0) {
133
- errors.push(`${filePath}: profiles must be a non-empty array; add a frontmatter line like \`profiles: [rnd]\``);
129
+ if (!Array.isArray(fm.scopes) && !Array.isArray(fm.profiles)) {
130
+ errors.push(`${filePath}: scopes must be a non-empty array; add \`scopes: [rnd]\``);
131
+ } else {
132
+ const scopes = fm.scopes ?? fm.profiles;
133
+ if (!Array.isArray(scopes) || scopes.length === 0) {
134
+ errors.push(`${filePath}: scopes must be a non-empty array; add \`scopes: [rnd]\``);
135
+ }
134
136
  }
135
137
  return errors;
136
138
  }
@@ -3,7 +3,7 @@
3
3
  * authoritative declarative catalogs.
4
4
  *
5
5
  * Ingests registry/capabilities.json, lib/embedded-contract/workflow-defs.mjs,
6
- * and specialists/contracts.json into typed nodes and directed edges:
6
+ * and specialists/org into typed nodes and directed edges:
7
7
  * capability --embeds--> workflow (embeddedWorkflow)
8
8
  * test --validates--> capability (verification.functional / hostEmulation / per-surface test)
9
9
  * capability --uses--> skill | rule (skill + dependencies.skills/.rules)
@@ -16,6 +16,7 @@
16
16
  */
17
17
 
18
18
  import { existsSync, readFileSync, statSync } from 'node:fs';
19
+ import { loadRegistry } from '../registry/loader.mjs';
19
20
  import path from 'node:path';
20
21
  import { createHash } from 'node:crypto';
21
22
 
@@ -31,6 +32,23 @@ function existsRel(rootDir, rel) {
31
32
  return !!rel && existsSync(path.join(rootDir, rel));
32
33
  }
33
34
 
35
+ function declarationImplFiles(cap, rootDir) {
36
+ const files = [];
37
+ if (cap.kind === 'workflow') {
38
+ files.push('lib/embedded-contract/workflow-invoke.mjs', 'lib/embedded-contract/workflow-defs.mjs');
39
+ }
40
+ if (cap.kind === 'document-type') {
41
+ files.push('lib/artifact-release-gate.mjs', 'lib/artifact-manifest.mjs');
42
+ }
43
+ if (cap.id === 'ingest.docling') {
44
+ files.push('lib/document-extract/docling-client.mjs', 'lib/ingest/docling-remote.mjs');
45
+ }
46
+ if (cap.id === 'mcp.tool-budget.trim') {
47
+ files.push('lib/mcp/tool-budget.mjs');
48
+ }
49
+ return [...new Set(files.filter((rel) => existsRel(rootDir, rel)))];
50
+ }
51
+
34
52
  function hashFiles(rootDir, rels) {
35
53
  const h = createHash('sha256');
36
54
  for (const rel of rels) {
@@ -66,11 +84,11 @@ export function buildFromRegistry({ rootDir }) {
66
84
  });
67
85
  }
68
86
 
69
- const contractsDoc = readJsonSafe(path.join(rootDir, 'specialists', 'contracts.json'));
70
- for (const c of contractsDoc?.contracts ?? []) {
71
- if (!c?.id) continue;
72
- node('contract', c.id, c.id, { producer: c.producer ?? null, consumer: c.consumer ?? null });
73
- }
87
+ const contractsDoc = loadRegistry({ rootDir });
88
+ for (const { id, producer, consumer } of Object.values(contractsDoc?.contracts ?? {})) {
89
+ if (!id) continue;
90
+ node('contract', id, id, { producer: producer ?? null, consumer: consumer ?? null });
91
+ }
74
92
 
75
93
  const registry = loadCapabilityRegistry({ rootDir });
76
94
  for (const cap of registry.capabilities ?? []) {
@@ -122,11 +140,17 @@ export function buildFromRegistry({ rootDir }) {
122
140
  node('contract', cid, cid, {});
123
141
  edge(capNode, 'governed_by', nodeId('contract', cid));
124
142
  }
143
+
144
+ for (const implRel of declarationImplFiles(cap, rootDir)) {
145
+ const fileNode = nodeId('file', implRel);
146
+ node('file', implRel, implRel, { path: implRel, exists: true, source: 'declaration' });
147
+ edge(fileNode, 'realizes', capNode);
148
+ }
125
149
  }
126
150
 
127
151
  const sourceHash = hashFiles(rootDir, [
128
152
  'registry/capabilities.json',
129
- 'specialists/contracts.json',
153
+ 'specialists/org',
130
154
  'lib/embedded-contract/workflow-defs.mjs',
131
155
  ]);
132
156
 
@@ -11,7 +11,7 @@ import { loadGraph } from './store.mjs';
11
11
 
12
12
  export const GRAPH_SEED_FILES = [
13
13
  'registry/capabilities.json',
14
- 'specialists/contracts.json',
14
+ 'specialists/org',
15
15
  'lib/embedded-contract/workflow-defs.mjs',
16
16
  ];
17
17
 
package/lib/headhunt.mjs CHANGED
@@ -13,15 +13,19 @@ import os from 'node:os';
13
13
  import path from 'node:path';
14
14
  import { createInterface } from 'node:readline';
15
15
  import { selectOption } from './tty-prompts.mjs';
16
+ import { resolveActiveScope } from './scopes/loader.mjs';
17
+ import { classifyObjectiveForScope } from './scopes/teams.mjs';
18
+ import { loadRegistry } from './registry/loader.mjs';
16
19
 
17
20
  const OVERLAY_DIRNAME = 'domain-overlays';
18
21
  const PROMOTION_DIRNAME = 'promotion-requests';
19
- const TEAMS_FILE = 'specialists/teams.json';
20
22
 
21
23
  function loadTeamTemplates(root) {
22
24
  try {
23
- const teamsPath = path.join(root, TEAMS_FILE);
24
- return JSON.parse(fs.readFileSync(teamsPath, 'utf8'));
25
+ const registry = loadRegistry({ rootDir: root });
26
+ return {
27
+ templates: registry.teams || {},
28
+ };
25
29
  } catch {
26
30
  return null;
27
31
  }
@@ -63,37 +67,70 @@ function promptQuestion(rl, question) {
63
67
  return new Promise((resolve) => rl.question(question, resolve));
64
68
  }
65
69
 
66
- function classifyOverlay(objective = '', explicitTeam = '') {
70
+ export function classifyOverlay(objective = '', explicitTeam = '', { cwd = process.cwd() } = {}) {
67
71
  if (explicitTeam) {
68
72
  return {
69
73
  focus: 'custom',
70
74
  attachTo: explicitTeam.split(',').map((value) => value.trim()).filter(Boolean),
75
+ teamFocus: 'custom-team',
71
76
  };
72
77
  }
73
78
 
79
+ const profile = resolveActiveScope(cwd);
80
+ const profileMatch = classifyObjectiveForScope(objective, profile);
81
+ if (profileMatch) return profileMatch;
82
+
74
83
  const text = objective.toLowerCase();
84
+
85
+ // NEW: Team-first classification returns both team id and specialist roles
75
86
  if (/security|policy|compliance|secrets|iam|threat|audit/.test(text)) {
76
- return { focus: 'security', attachTo: ['cx-security', 'cx-architect'] };
87
+ return {
88
+ focus: 'security',
89
+ attachTo: ['cx-security', 'cx-architect'],
90
+ teamFocus: 'governance-group',
91
+ recommendedTeam: 'governance-group',
92
+ };
77
93
  }
78
94
  if (/implement|build|module|deploy|pipeline|ci\/cd|apply|code/.test(text)) {
79
- return { focus: 'implementation', attachTo: ['cx-engineer', 'cx-architect'] };
95
+ return {
96
+ focus: 'implementation',
97
+ attachTo: ['cx-engineer', 'cx-architect'],
98
+ teamFocus: 'engineering-group',
99
+ recommendedTeam: 'engineering-group',
100
+ };
80
101
  }
81
102
  if (/runbook|docs|documentation|guide|onboard/.test(text)) {
82
- return { focus: 'documentation', attachTo: ['cx-docs-keeper', 'cx-researcher'] };
103
+ return {
104
+ focus: 'documentation',
105
+ attachTo: ['cx-docs-keeper', 'cx-researcher'],
106
+ teamFocus: 'operations-group',
107
+ recommendedTeam: 'operations-group',
108
+ };
83
109
  }
84
110
  if (/research|compare|recent|latest|best practices|upgrade|migration/.test(text)) {
85
- return { focus: 'research', attachTo: ['cx-researcher', 'cx-architect'] };
111
+ return {
112
+ focus: 'research',
113
+ attachTo: ['cx-researcher', 'cx-architect'],
114
+ teamFocus: 'strategy-group',
115
+ recommendedTeam: 'strategy-group',
116
+ };
86
117
  }
87
- return { focus: 'architecture', attachTo: ['cx-architect', 'cx-researcher'] };
118
+ return {
119
+ focus: 'architecture',
120
+ attachTo: ['cx-architect', 'cx-researcher'],
121
+ teamFocus: 'engineering-group',
122
+ recommendedTeam: 'engineering-group',
123
+ };
88
124
  }
89
125
 
90
- function buildOverlayPrompt({ domain, objective, scope, permanence, focus, freshness }) {
126
+ function buildOverlayPrompt({ domain, objective, scope, permanence, focus, freshness, teamFocus = '' }) {
91
127
  return [
92
128
  `Domain overlay: ${domain}`,
93
129
  `Objective: ${objective}`,
94
130
  scope ? `Scope: ${scope}` : null,
95
131
  `Permanence: ${permanence}`,
96
132
  `Primary focus: ${focus}`,
133
+ teamFocus ? `Team focus: ${teamFocus}` : null,
97
134
  `Freshness requirement: ${freshness}`,
98
135
  'Rules:',
99
136
  '- Treat this as an internal Construct overlay, not a permanent public role.',
@@ -102,6 +139,7 @@ function buildOverlayPrompt({ domain, objective, scope, permanence, focus, fresh
102
139
  '- Separate confirmed facts from inference.',
103
140
  '- Respect existing Construct workflow, security, and validation boundaries.',
104
141
  '- Do not broaden tool permissions just because the domain is specialized.',
142
+ '- NEW: Team-first approach — work within the team structure and escalation paths defined in your organization.',
105
143
  ].filter(Boolean).join('\n');
106
144
  }
107
145
 
@@ -342,7 +380,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
342
380
  if (!templateName) { listTeamTemplates(cwd); return; }
343
381
  const root = findConstructRoot(cwd);
344
382
  const teams = loadTeamTemplates(root);
345
- if (!teams) throw new Error('specialists/teams.json not found. Run from the Construct root.');
383
+ if (!teams) throw new Error('specialists/org not found. Run from the Construct root.');
346
384
  const tpl = teams.templates[templateName];
347
385
  if (!tpl) throw new Error(`Unknown template: ${templateName}. Run 'construct headhunt template' to list available templates.`);
348
386
  const objective = typeof flags.for === 'string' ? flags.for.trim() : rest.slice(1).join(' ').trim();
@@ -354,29 +392,30 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
354
392
  const overlayDir = getOverlayDir(root);
355
393
  const expiresAt = permanence === 'temporary' ? new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString() : null;
356
394
 
357
- const overlay = {
358
- id: overlayId,
359
- type: 'domain-overlay',
360
- domain: templateName,
361
- objective,
362
- scope: typeof flags.scope === 'string' ? flags.scope.trim() : null,
363
- permanence,
364
- focus: tpl.focus,
365
- attachTo: tpl.members,
366
- freshness,
367
- teamTemplate: templateName,
368
- teamId: overlayId,
369
- createdAt: now.toISOString(),
370
- expiresAt,
371
- project: path.basename(root),
372
- workflowId: null,
373
- taskKey: null,
374
- status: 'active',
375
- prompt: buildOverlayPrompt({ domain: templateName, objective, scope: typeof flags.scope === 'string' ? flags.scope : '', permanence, focus: tpl.focus, freshness }),
376
- skills: tpl.skills ?? [],
377
- promotionGates: tpl.promotionGates ?? buildPromotionGates(),
378
- challenge: null,
379
- };
395
+ const overlay = {
396
+ id: overlayId,
397
+ type: 'domain-overlay',
398
+ domain: templateName,
399
+ objective,
400
+ scope: typeof flags.scope === 'string' ? flags.scope.trim() : null,
401
+ permanence,
402
+ focus: tpl.focus,
403
+ attachTo: tpl.members,
404
+ freshness,
405
+ teamTemplate: templateName,
406
+ teamId: overlayId,
407
+ teamFocus: tpl.focus,
408
+ createdAt: now.toISOString(),
409
+ expiresAt,
410
+ project: path.basename(root),
411
+ workflowId: null,
412
+ taskKey: null,
413
+ status: 'active',
414
+ prompt: buildOverlayPrompt({ domain: templateName, objective, scope: typeof flags.scope === 'string' ? flags.scope : '', permanence, focus: tpl.focus, freshness, teamFocus: tpl.focus }),
415
+ skills: tpl.skills ?? [],
416
+ promotionGates: tpl.promotionGates ?? buildPromotionGates(),
417
+ challenge: null,
418
+ };
380
419
 
381
420
  const overlayJsonPath = path.join(overlayDir, `${overlayId}.json`);
382
421
  const overlayMdPath = path.join(overlayDir, `${overlayId}.md`);
@@ -448,7 +487,11 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
448
487
  }
449
488
 
450
489
  const freshness = typeof flags.freshness === 'string' ? flags.freshness : 'current';
451
- const classification = classifyOverlay(objective, typeof flags.team === 'string' ? flags.team : '');
490
+ const classification = classifyOverlay(
491
+ objective,
492
+ typeof flags.team === 'string' ? flags.team : '',
493
+ { cwd },
494
+ );
452
495
  const now = new Date();
453
496
  const root = findConstructRoot(cwd);
454
497
  const overlayId = `${slugify(domain)}-${now.toISOString().replace(/[:.]/g, '-')}`;
@@ -473,6 +516,9 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
473
516
  taskKey: null,
474
517
  status: permanence === 'temporary' ? 'active' : 'promotion_requested',
475
518
  teamId: overlayId,
519
+ teamFocus: classification.teamFocus || classification.focus,
520
+ recommendedTeam: classification.recommendedTeam || null,
521
+ scopeTeamSource: classification.scopeTeamSource || null,
476
522
  sourceStrategy: [
477
523
  'official docs',
478
524
  'release notes/changelogs',
@@ -488,6 +534,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
488
534
  permanence,
489
535
  focus: classification.focus,
490
536
  freshness,
537
+ teamFocus: classification.teamFocus,
491
538
  }),
492
539
  promotionGates: buildPromotionGates(),
493
540
  challenge: permanence === 'temporary' ? null : {
@@ -155,10 +155,10 @@ try {
155
155
  if (success !== null) {
156
156
  try {
157
157
  const { recordOutcome } = await import('../outcomes/record.mjs');
158
- const { resolveActiveProfile } = await import('../profiles/loader.mjs');
158
+ const { resolveActiveScope } = await import('../scopes/loader.mjs');
159
159
  const roleId = agentName.replace(/^cx-/, '');
160
160
  const projectCwd = input?.cwd || process.cwd();
161
- const activeProfile = resolveActiveProfile(projectCwd);
161
+ const activeProfile = resolveActiveScope(projectCwd);
162
162
  recordOutcome(projectCwd, {
163
163
  role: roleId,
164
164
  success,
@@ -52,7 +52,7 @@ const PROTECTED = [
52
52
  ];
53
53
 
54
54
  const META_FILES = [
55
- /(?:^|\/)specialists\/registry\.json$/,
55
+ /(?:^|\/)specialists\/org(?:\/|$)/,
56
56
  /(?:^|\/)lib\/setup\.mjs$/,
57
57
  /(?:^|\/)bin\/construct-postinstall\.mjs$/,
58
58
  /(?:^|\/)claude\/settings\.template\.json$/,
@@ -3,11 +3,11 @@
3
3
  * lib/hooks/registry-sync.mjs — Registry-change reminder hook.
4
4
  *
5
5
  * PostToolUse on Write|Edit. Fires only when the edited file is
6
- * specialists/registry.json (the construct repo) or an installed
6
+ * specialists/org/ (the construct repo) or an installed
7
7
  * agents/registry.json mirror. Emits a one-line reminder on stderr telling
8
8
  * the developer to run `construct sync` when they're ready. Does NOT
9
9
  * auto-execute sync — auto-execution during tests (which legitimately
10
- * mutate registry.json) races test cleanup and regenerates platform state
10
+ * mutate the registry) races test cleanup and regenerates platform state
11
11
  * mid-suite. The dev decides when to sync, the same way ordinary code
12
12
  * changes don't auto-build.
13
13
  *
@@ -30,7 +30,7 @@ const input = readHookInput();
30
30
  const filePath = input?.tool_input?.file_path || process.env.TOOL_INPUT_FILE_PATH || '';
31
31
  const matchesRegistry =
32
32
  filePath.endsWith(path.join('agents', 'registry.json')) ||
33
- filePath.endsWith('/specialists/registry.json');
33
+ filePath.includes(`${path.sep}specialists${path.sep}org`);
34
34
  if (!matchesRegistry) process.exit(0);
35
35
 
36
36
  const isNonInteractive =