@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
@@ -48,6 +48,43 @@ export function sanitizeOpenCodeConfig(config) {
48
48
  return sanitized;
49
49
  }
50
50
 
51
+ // op run resolves only the launch env-file, never values baked into config
52
+ // JSON. A literal `op://` reference (or a build-time __PLACEHOLDER__) left in a
53
+ // credential field is therefore sent to OpenRouter verbatim, which 401s. Only
54
+ // `{env:VAR}` (resolved by OpenCode) and concrete keys count as real auth.
55
+
56
+ function isUnresolvedSecretRef(value) {
57
+ return typeof value === 'string' && (value.includes('op://') || value.includes('__OPENROUTER_API_KEY__'));
58
+ }
59
+
60
+ function openRouterHasAuth(provider = {}) {
61
+ const apiKey = provider.apiKey;
62
+ if (typeof apiKey === 'string' && apiKey.length > 0 && !isUnresolvedSecretRef(apiKey)) {
63
+ return true;
64
+ }
65
+ const auth = provider.options?.headers?.Authorization;
66
+ return typeof auth === 'string' && auth.length > 0 && !isUnresolvedSecretRef(auth);
67
+ }
68
+
69
+ export function ensureOpenRouterProviderAuth(config) {
70
+ if (!config?.provider?.openrouter) return config;
71
+ const provider = config.provider.openrouter;
72
+
73
+ // A broken Authorization header overrides the working apiKey at request time,
74
+ // so strip it before deciding whether the provider still needs an env-ref key.
75
+
76
+ const authHeader = provider.options?.headers?.Authorization;
77
+ if (isUnresolvedSecretRef(authHeader)) {
78
+ delete provider.options.headers.Authorization;
79
+ }
80
+ if (openRouterHasAuth(provider)) return config;
81
+ config.provider.openrouter = {
82
+ ...provider,
83
+ apiKey: '{env:OPENROUTER_API_KEY}',
84
+ };
85
+ return config;
86
+ }
87
+
51
88
  /**
52
89
  * Maps Construct internal tiers (fast, standard, reasoning) to OpenCode model configurations.
53
90
  */
@@ -124,7 +161,7 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
124
161
  if (config.models.some(m => m.provider === 'openrouter')) {
125
162
  config.provider.openrouter = config.provider.openrouter || {
126
163
  baseUrl: "https://openrouter.ai/api/v1",
127
- apiKey: "__OPENROUTER_API_KEY__"
164
+ apiKey: "{env:OPENROUTER_API_KEY}"
128
165
  };
129
166
  }
130
167
  if (config.models.some(m => m.provider === 'ollama')) {
@@ -204,6 +241,24 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
204
241
  // The relevant models should only be defined under provider.<name>.models
205
242
  delete config.models;
206
243
 
244
+ ensureOpenRouterProviderAuth(config);
245
+
246
+ // The plugin array is hand- and CLI-editable, so it accumulates junk: stale
247
+ // toolkit paths from moved checkouts and stray CLI tokens (e.g. a literal
248
+ // "list"). A non-existent path entry can break plugin loading outright. Keep
249
+ // existing file-path plugins and plausible package specifiers, deduped.
250
+
251
+ if (Array.isArray(config.plugin)) {
252
+ const seen = new Set();
253
+ config.plugin = config.plugin.filter((entry) => {
254
+ if (typeof entry !== "string" || !entry.trim() || seen.has(entry)) return false;
255
+ seen.add(entry);
256
+ const isPath = /^([./~]|[A-Za-z]:[\\/])/.test(entry) || /\.[cm]?js$/.test(entry);
257
+ if (isPath) return fs.existsSync(entry);
258
+ return entry.length > 2 && (/^@/.test(entry) || /[-/]/.test(entry));
259
+ });
260
+ }
261
+
207
262
  fs.writeFileSync(target, `${JSON.stringify(sanitizeOpenCodeConfig(config), null, 2)}\n`, "utf8");
208
263
  return target;
209
264
  }
@@ -5,6 +5,7 @@
5
5
  * records each as a telemetry trace, and applies model fallback on rate-limit errors.
6
6
  */
7
7
  import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
8
+ import { loadRegistry } from './registry/loader.mjs';
8
9
  import { homedir } from "node:os";
9
10
  import { dirname, join, resolve } from "node:path";
10
11
  import { spawnSync } from "node:child_process";
@@ -416,9 +417,7 @@ function buildHostConstraints() {
416
417
 
417
418
  function readRegistryModels(rootDir) {
418
419
  try {
419
- const registryPath = join(rootDir, "specialists", "registry.json");
420
- if (!existsSync(registryPath)) return {};
421
- const registry = JSON.parse(readFileSync(registryPath, "utf8"));
420
+ const registry = loadRegistry({ rootDir, skipValidation: true });
422
421
  return registry?.models ?? {};
423
422
  } catch {
424
423
  return {};
@@ -759,13 +758,17 @@ async function emitRuntimeTrace(event, { client, env = process.env }) {
759
758
  await ingest.flush();
760
759
  }
761
760
  } catch {
762
- await client?.app?.log?.({
763
- body: {
764
- service: "construct",
765
- level: "warn",
766
- message: "OpenCode runtime telemetry failed before delivery.",
767
- },
768
- });
761
+ try {
762
+ await client?.app?.log?.({
763
+ body: {
764
+ service: "construct",
765
+ level: "warn",
766
+ message: "OpenCode runtime telemetry failed before delivery.",
767
+ },
768
+ });
769
+ } catch {
770
+ // best effort: telemetry failure must not block fallback handling
771
+ }
769
772
  }
770
773
  }
771
774
 
@@ -49,6 +49,10 @@ function collectBypassArtifacts(projectDir) {
49
49
  }
50
50
 
51
51
  function collectReviewerGaps(projectDir) {
52
+ const agentLog = path.join(projectDir, '.cx', 'agent-log.jsonl');
53
+ if (!fs.existsSync(agentLog)) {
54
+ return { gaps: [], armed: false };
55
+ }
52
56
  const roots = [
53
57
  path.join(projectDir, 'docs', 'specs', 'prd'),
54
58
  path.join(projectDir, 'docs', 'prd'),
@@ -70,12 +74,12 @@ function collectReviewerGaps(projectDir) {
70
74
  }
71
75
  }
72
76
  }
73
- return gaps.slice(0, 20);
77
+ return { gaps: gaps.slice(0, 20), armed: true };
74
78
  }
75
79
 
76
80
  export function collectArtifactGateSignals({ rootDir, projectDir }) {
77
81
  const bypassed = collectBypassArtifacts(projectDir);
78
- const reviewerGaps = collectReviewerGaps(projectDir);
82
+ const { gaps: reviewerGaps, armed: reviewerGateArmed } = collectReviewerGaps(projectDir);
79
83
 
80
84
  let specialistAudit = { present: false, pass: true, crossCheckCount: 0 };
81
85
  if (isConstructPackageRepo(projectDir)) {
@@ -87,8 +91,13 @@ export function collectArtifactGateSignals({ rootDir, projectDir }) {
87
91
  crossCheckCount: audit.crossCheckIssues?.length ?? 0,
88
92
  crossCheckIssues: (audit.crossCheckIssues ?? []).slice(0, 5),
89
93
  };
90
- } catch {
91
- specialistAudit = { present: true, pass: false, crossCheckCount: -1 };
94
+ } catch (err) {
95
+ specialistAudit = {
96
+ present: true,
97
+ pass: false,
98
+ crossCheckCount: 0,
99
+ error: err?.message ?? String(err),
100
+ };
92
101
  }
93
102
  }
94
103
 
@@ -97,6 +106,7 @@ export function collectArtifactGateSignals({ rootDir, projectDir }) {
97
106
  bypassCount: bypassed.length,
98
107
  reviewerGaps,
99
108
  reviewerGapCount: reviewerGaps.length,
109
+ reviewerGateArmed,
100
110
  specialistAudit,
101
111
  };
102
112
  }
@@ -21,6 +21,7 @@ import { runOracleTick, listPending, approvePending, defaultRootDir } from './ac
21
21
  import { collectOracleGaps, formatOracleGapsReport } from './gaps.mjs';
22
22
  import { reconcileOracleHygieneBeads } from './reconcile.mjs';
23
23
  import { doctorRoot } from '../config/xdg.mjs';
24
+ import { writeLastTick } from './index.mjs';
24
25
 
25
26
  const MODULE_FILE = fileURLToPath(import.meta.url);
26
27
 
@@ -87,6 +88,7 @@ async function cmdReview(args, { rootDir, projectDir, homeDir }) {
87
88
  const dryRun = args.includes('--dry-run');
88
89
  const json = args.includes('--json');
89
90
  const result = await runOracleTick({ rootDir, projectDir, homeDir, dryRun });
91
+ if (!dryRun) writeLastTick(result.tick, homeDir);
90
92
  if (json) {
91
93
  process.stdout.write(JSON.stringify(result, null, 2) + '\n');
92
94
  return 0;
@@ -9,6 +9,7 @@ import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
 
11
11
  import { routeGap, routeAction } from './routing.mjs';
12
+ import { resolveRemediationDispatch } from './remediation-dispatch.mjs';
12
13
 
13
14
  export function routingDir(projectDir) {
14
15
  return path.join(projectDir, '.cx', 'oracle', 'routing');
@@ -28,9 +29,16 @@ export function buildRoutingArtifact({ synthesis, readModel, tickId, route }) {
28
29
 
29
30
  for (const gap of synthesis.gaps ?? []) {
30
31
  const r = routeGap(gap);
32
+ const dispatch = gap.remediationRoute?.mode
33
+ ? gap.remediationRoute
34
+ : resolveRemediationDispatch(gap);
31
35
  lines.push(` [${gap.severity}] ${gap.id} — ${gap.detail}`);
32
36
  lines.push(` signal: ${gap.signal ?? 'unknown'}`);
33
37
  lines.push(` route: ${r.primary}${r.secondary ? ` (secondary: ${r.secondary})` : ''}`);
38
+ lines.push(` dispatch: ${dispatch.mode}${dispatch.teamRouting?.involvedTeams?.length ? ` (teams: ${dispatch.teamRouting.involvedTeams.join(', ')})` : ''}`);
39
+ if (dispatch.mode === 'swarm' && dispatch.specialists?.length) {
40
+ lines.push(` swarm: ${dispatch.specialists.join(', ')}`);
41
+ }
34
42
  if (r.gateType) lines.push(` gate: ${r.gateType}`);
35
43
  }
36
44
 
@@ -50,6 +58,15 @@ export function buildRoutingArtifact({ synthesis, readModel, tickId, route }) {
50
58
  if (route?.primary) {
51
59
  lines.push('', `DISPATCH TARGET: ${route.primary}`);
52
60
  }
61
+ if (route?.mode) {
62
+ lines.push(`DISPATCH MODE: ${route.mode}`);
63
+ if (route.mode === 'swarm' && route.specialists?.length) {
64
+ lines.push(`SWARM SPECIALISTS: ${route.specialists.join(', ')}`);
65
+ }
66
+ if (route.teamRouting?.involvedTeams?.length) {
67
+ lines.push(`INVOLVED TEAMS: ${route.teamRouting.involvedTeams.join(', ')}`);
68
+ }
69
+ }
53
70
 
54
71
  lines.push('', 'BLOCKED:', ' none (deterministic routing artifact — LLM dispatch is approve-gated)');
55
72
  return lines.join('\n');
@@ -76,6 +93,6 @@ export function maybeWriteHighSeverityRouting({ projectDir, tick, synthesis, rea
76
93
  if (!hasHigh) return null;
77
94
  const tickId = tick.at ?? new Date().toISOString();
78
95
  const primaryGap = synthesis.gaps.find((g) => g.severity === 'high');
79
- const route = routeGap(primaryGap);
96
+ const route = primaryGap?.remediationRoute ?? routeGap(primaryGap);
80
97
  return writeRoutingArtifact({ projectDir, tickId, synthesis, readModel, route });
81
98
  }
@@ -16,6 +16,7 @@ import { recordAndMaybeInvoke } from '../roles/gateway.mjs';
16
16
  import { validateCapabilityRegistry } from '../registry/validate.mjs';
17
17
  import { writeRoutingArtifact } from './dispatch.mjs';
18
18
  import { routeAction } from './routing.mjs';
19
+ import { resolveRemediationDispatch } from './remediation-dispatch.mjs';
19
20
  import { doctorRoot } from '../config/xdg.mjs';
20
21
 
21
22
  function rolePendingPath(homeDir = homedir()) {
@@ -30,14 +31,14 @@ function appendRolePending(entry, homeDir = homedir()) {
30
31
  fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8');
31
32
  }
32
33
 
33
- async function dispatchSpecialist({ action, projectDir, homeDir, personaId, eventType }) {
34
+ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, eventType, dispatch = null }) {
34
35
  const route = routeAction(action.kind);
35
36
  const artifactPath = writeRoutingArtifact({
36
37
  projectDir,
37
38
  tickId: action.id,
38
39
  synthesis: { verdict: 'approved', gaps: [], recommendedActions: [action] },
39
40
  readModel: null,
40
- route,
41
+ route: dispatch ?? route,
41
42
  });
42
43
 
43
44
  const prevRolesRoot = process.env.CONSTRUCT_ROLES_ROOT;
@@ -53,6 +54,8 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
53
54
  artifactPath,
54
55
  gateType: route.gateType,
55
56
  requiredApprover: route.primary,
57
+ dispatchMode: dispatch?.mode ?? 'static',
58
+ involvedTeams: dispatch?.teamRouting?.involvedTeams ?? [],
56
59
  },
57
60
  };
58
61
 
@@ -69,11 +72,12 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
69
72
  personaId: personaId.replace(/^cx-/, ''),
70
73
  cxId: personaId,
71
74
  bdIssueId: gatewayResult?.bdIssueId ?? null,
72
- fingerprint: `oracle-${action.id}`,
75
+ fingerprint: `oracle-${action.id}-${personaId}`,
73
76
  eventType,
74
77
  summary: action.summary,
75
78
  source: 'oracle-execute',
76
79
  artifactPath,
80
+ dispatchMode: dispatch?.mode ?? 'static',
77
81
  }, homeDir);
78
82
  }
79
83
 
@@ -88,6 +92,34 @@ async function dispatchSpecialist({ action, projectDir, homeDir, personaId, even
88
92
  };
89
93
  }
90
94
 
95
+ async function dispatchRemediation({ action, projectDir, homeDir, eventType, personaId = null }) {
96
+ const dispatch = resolveRemediationDispatch(action, { projectDir });
97
+ if (dispatch.mode === 'swarm') {
98
+ const results = [];
99
+ for (const specialistId of dispatch.specialists) {
100
+ results.push(await dispatchSpecialist({
101
+ action, projectDir, homeDir, personaId: specialistId, eventType, dispatch,
102
+ }));
103
+ }
104
+ return {
105
+ ok: true,
106
+ mode: 'swarm',
107
+ specialists: dispatch.specialists,
108
+ teamRouting: dispatch.teamRouting,
109
+ results,
110
+ };
111
+ }
112
+ const result = await dispatchSpecialist({
113
+ action,
114
+ projectDir,
115
+ homeDir,
116
+ personaId: personaId ?? dispatch.primary,
117
+ eventType,
118
+ dispatch,
119
+ });
120
+ return { ...result, mode: 'static', teamRouting: dispatch.teamRouting };
121
+ }
122
+
91
123
  /**
92
124
  * Execute an approved Oracle pending action.
93
125
  *
@@ -122,20 +154,20 @@ export async function executeApprovedAction(action, { rootDir, projectDir, homeD
122
154
  return { ok: result.status === 0, kind, exitCode: result.status };
123
155
  }
124
156
  case 'specialist-review':
125
- return dispatchSpecialist({
157
+ return dispatchRemediation({
126
158
  action, projectDir, homeDir, personaId: 'cx-reviewer', eventType: 'contract.violation',
127
159
  });
128
160
  case 'trace-review':
129
- return dispatchSpecialist({
161
+ return dispatchRemediation({
130
162
  action, projectDir, homeDir, personaId: 'cx-trace-reviewer', eventType: 'outcomes.degraded',
131
163
  });
132
164
  case 'doctor-followup':
133
- return dispatchSpecialist({
165
+ return dispatchRemediation({
134
166
  action, projectDir, homeDir, personaId: 'cx-sre', eventType: 'service.down',
135
167
  });
136
168
  case 'executive-signoff-required':
137
169
  case 'structure-cleanup-proposal':
138
- return dispatchSpecialist({
170
+ return dispatchRemediation({
139
171
  action, projectDir, homeDir, personaId: routeAction(kind).primary, eventType: 'oracle.approval',
140
172
  });
141
173
  default:
@@ -29,7 +29,7 @@ export function lastTickPath(homeDir = homedir()) {
29
29
  return path.join(runtimeDir(homeDir), 'last-tick.json');
30
30
  }
31
31
 
32
- function writeLastTick(tick, homeDir) {
32
+ export function writeLastTick(tick, homeDir = homedir()) {
33
33
  const dir = runtimeDir(homeDir);
34
34
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
35
35
  fs.writeFileSync(lastTickPath(homeDir), JSON.stringify(tick, null, 2));
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'node:url';
12
12
  import { alignmentFindings } from '../workflow-state.mjs';
13
13
  import { loadCapabilityRegistry } from '../registry/validate.mjs';
14
14
  import { isConstructPackageRepo } from '../host-disposition.mjs';
15
+ import { loadRegistry } from '../registry/loader.mjs';
15
16
 
16
17
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
17
18
  const PACKAGE_ROOT = path.resolve(MODULE_DIR, '../..');
@@ -25,10 +26,14 @@ function readJsonSafe(filePath) {
25
26
  }
26
27
 
27
28
  function collectPolicyInventory(rootDir) {
28
- const file = path.join(rootDir, 'specialists', 'policy-inventory.json');
29
- const data = readJsonSafe(file);
30
- if (!data) return { present: false, count: 0, policies: [] };
31
- const policies = data.policies ?? [];
29
+ let policies = [];
30
+ try {
31
+ const registry = loadRegistry({ rootDir, skipValidation: true });
32
+ const raw = registry.policies || {};
33
+ policies = Array.isArray(raw) ? raw : Object.values(raw);
34
+ } catch {
35
+ return { present: false, count: 0, policies: [] };
36
+ }
32
37
  const orphans = [];
33
38
  for (const p of policies) {
34
39
  if (p.source && p.source !== 'built-in' && !p.source.startsWith('specialists/')) {
@@ -79,7 +84,7 @@ function collectCapabilityGates(rootDir) {
79
84
  }
80
85
 
81
86
  function collectPropagationSignals({ rootDir, projectDir, parity, registryValidate }) {
82
- const registryPath = path.join(rootDir, 'specialists', 'registry.json');
87
+ const registryPath = path.join(rootDir, 'specialists', 'org');
83
88
  let registryMtime = null;
84
89
  try {
85
90
  registryMtime = fs.statSync(registryPath).mtimeMs;
@@ -30,6 +30,12 @@ export const VERDICT_ONLY_GAP_IDS = new Set([
30
30
  'propagation-stale',
31
31
  'census-stale',
32
32
  'outcomes-missing',
33
+ // Team governance signals require specialist review and cross-team discussion.
34
+ // They are not auto-actionable and surface in doctor/prelude as informational gaps.
35
+ 'team-understaffed',
36
+ 'escalation-path-broken',
37
+ 'team-decision-violation',
38
+ 'cross-team-handoff-blocked',
33
39
  ]);
34
40
 
35
41
  const DENY_ACTIONS = new Set([
@@ -9,6 +9,7 @@
9
9
  */
10
10
 
11
11
  import fs from 'node:fs';
12
+ import { loadRegistry } from '../registry/loader.mjs';
12
13
  import path from 'node:path';
13
14
  import { spawnSync } from 'node:child_process';
14
15
 
@@ -20,6 +21,7 @@ import { collectOrgGraph } from './org-graph.mjs';
20
21
  import { collectArtifactGateSignals } from './artifact-gate.mjs';
21
22
  import { loadGraph, nodesByType, dependentsOf } from '../graph/store.mjs';
22
23
  import { hashFiles } from '../graph/build-from-registry.mjs';
24
+ import { GRAPH_SEED_FILES } from '../graph/staleness.mjs';
23
25
  import { readViolationSupersedeCutoff } from '../contracts/violation-log.mjs';
24
26
  import { doctorRoot } from '../config/xdg.mjs';
25
27
 
@@ -235,12 +237,6 @@ function collectStructureSprawl(projectDir) {
235
237
  }
236
238
  }
237
239
 
238
- const GRAPH_SEED_FILES = [
239
- 'registry/capabilities.json',
240
- 'specialists/contracts.json',
241
- 'lib/embedded-contract/workflow-defs.mjs',
242
- ];
243
-
244
240
  // The living dependency matrix: file↔capability↔workflow↔test graph at
245
241
  // .cx/graph/. Surfaces three oversight signals — staleness (seeds changed
246
242
  // since last build), coverage (capabilities/workflows with no impl or test
@@ -314,6 +310,105 @@ function collectDeadCodeSync(rootDir, projectDir) {
314
310
  return { present: false, pendingAsync: true, deadCount: 0, regressions: [] };
315
311
  }
316
312
 
313
+ function specialistTeamId(specialist) {
314
+ return specialist.teamId ?? specialist.team ?? null;
315
+ }
316
+
317
+ function specialistsForTeamEntity(teamId, team, registry) {
318
+ const all = Object.values(registry.specialists ?? {});
319
+ if (team.kind === 'group' && Array.isArray(team.squads) && team.squads.length > 0) {
320
+ const squadIds = new Set(team.squads);
321
+ return all.filter((s) => squadIds.has(specialistTeamId(s)));
322
+ }
323
+ return all.filter((s) => specialistTeamId(s) === teamId);
324
+ }
325
+
326
+ function isTeamStaffed(teamId, team, registry) {
327
+ if (specialistsForTeamEntity(teamId, team, registry).length > 0) return true;
328
+ if (team.kind === 'squad' && Array.isArray(team.specialists) && team.specialists.length > 0) {
329
+ return true;
330
+ }
331
+ return false;
332
+ }
333
+
334
+ function ownerExistsForTeam(teamId, team, registry) {
335
+ const ownerRole = team.owner;
336
+ if (!ownerRole) return true;
337
+ return specialistsForTeamEntity(teamId, team, registry).some((s) => s.role === ownerRole);
338
+ }
339
+
340
+ function collectTeamGovernance(rootDir) {
341
+ const registry = loadRegistry({ rootDir });
342
+ if (!registry?.teams) {
343
+ return { present: false, teams: {} };
344
+ }
345
+
346
+ const teams = {};
347
+ for (const [teamId, team] of Object.entries(registry.teams)) {
348
+ const roles = team.roles ?? [];
349
+ const escalationPath = team.escalationPath ?? [];
350
+ const decisionRights = team.decisionRights ?? [];
351
+ const forbiddenDecisions = team.forbiddenDecisions ?? [];
352
+ const teamSpecialists = specialistsForTeamEntity(teamId, team, registry);
353
+
354
+ // Check if escalation path is complete (all roles exist in registry)
355
+ const escalationPathBroken = escalationPath.some((role) => {
356
+ const specialist = Object.values(registry.specialists ?? {}).some((s) => s.role === role);
357
+ return !specialist;
358
+ });
359
+
360
+ teams[teamId] = {
361
+ id: teamId,
362
+ name: team.name,
363
+ kind: team.kind ?? null,
364
+ owner: team.owner,
365
+ roleCount: roles.length,
366
+ specialistCount: teamSpecialists.length,
367
+ decisionRightsCount: decisionRights.length,
368
+ forbiddenDecisionsCount: forbiddenDecisions.length,
369
+ escalationPathLength: escalationPath.length,
370
+ escalationPathBroken,
371
+ ownerExists: ownerExistsForTeam(teamId, team, registry),
372
+ understaffed: roles.length > 0 && !isTeamStaffed(teamId, team, registry),
373
+ };
374
+ }
375
+
376
+ // Cross-team handoff health: a crossing contract whose required approver team
377
+ // is missing or unstaffed can never be approved, so the handoff is blocked.
378
+ const contracts = Array.isArray(registry.contracts)
379
+ ? registry.contracts
380
+ : Object.values(registry.contracts ?? {});
381
+ const crossTeamHandoffsBlocked = [];
382
+ for (const contract of contracts) {
383
+ const boundary = contract.teamBoundary;
384
+ if (!boundary?.crosses
385
+ || !Array.isArray(boundary.requiresApprovalFrom)
386
+ || boundary.requiresApprovalFrom.length === 0) {
387
+ continue;
388
+ }
389
+ const unstaffedApprovers = boundary.requiresApprovalFrom.filter((approverId) => {
390
+ const team = registry.teams?.[approverId];
391
+ if (!team) return true;
392
+ return !isTeamStaffed(approverId, team, registry);
393
+ });
394
+ if (unstaffedApprovers.length > 0) {
395
+ crossTeamHandoffsBlocked.push({
396
+ contract: contract.id,
397
+ producerTeam: boundary.producerTeam ?? null,
398
+ consumerTeam: boundary.consumerTeam ?? null,
399
+ blockedBy: unstaffedApprovers,
400
+ });
401
+ }
402
+ }
403
+
404
+ return {
405
+ present: true,
406
+ teamCount: Object.keys(teams).length,
407
+ teams,
408
+ crossTeamHandoffsBlocked,
409
+ };
410
+ }
411
+
317
412
  /**
318
413
  * Collect the Oracle read model from durable Construct signals.
319
414
  *
@@ -334,6 +429,7 @@ export function collectReadModel({ rootDir, projectDir, homeDir }) {
334
429
  const registryValidate = collectRegistryValidate(rootDir);
335
430
  const orgGraph = collectOrgGraph({ rootDir, projectDir, parity, registryValidate });
336
431
  const artifactGate = collectArtifactGateSignals({ rootDir, projectDir });
432
+ const teamGovernance = collectTeamGovernance(rootDir);
337
433
 
338
434
  return {
339
435
  collectedAt,
@@ -349,6 +445,7 @@ export function collectReadModel({ rootDir, projectDir, homeDir }) {
349
445
  parity,
350
446
  orgGraph,
351
447
  artifactGate,
448
+ teamGovernance,
352
449
  beads: collectBeadsOpenCount(projectDir),
353
450
  hookFailures: collectHookFailures(homeDir),
354
451
  structure: collectStructureSprawl(projectDir),
@@ -93,6 +93,20 @@ function pruneRaisedIssues(projectDir, { dryRun = false } = {}) {
93
93
 
94
94
  const BARE_GOAL_FIELD_LABELS = ['intent', 'workCategory', 'riskFlags', 'acceptanceCriteria'];
95
95
 
96
+ const RESEARCHER_PROBE_MISSING = [
97
+ 'artifact missing required field: question',
98
+ 'artifact missing required field: method',
99
+ 'artifact missing required field: sources',
100
+ 'artifact missing required field: findings',
101
+ 'artifact missing required field: confidence',
102
+ ];
103
+
104
+ const A11Y_PROBE_MISSING = [
105
+ 'artifact missing required field: findings',
106
+ 'artifact missing required field: wcagCriterion',
107
+ 'artifact missing required field: userImpact',
108
+ ];
109
+
96
110
  function missingBareGoalConstructFields(missing) {
97
111
  if (!Array.isArray(missing) || missing.length !== BARE_GOAL_FIELD_LABELS.length) return false;
98
112
  return BARE_GOAL_FIELD_LABELS.every((field) =>
@@ -107,6 +121,19 @@ function isBareGoalConstructViolation(row) {
107
121
  return Array.isArray(keys) && keys.length === 1 && keys[0] === 'goal';
108
122
  }
109
123
 
124
+ function isEmptyProbeViolation(row, contractId, expectedMissing) {
125
+ if (row?.contractId !== contractId) return false;
126
+ const keys = row.packet_keys ?? row.packetKeys;
127
+ if (!Array.isArray(keys) || keys.length !== 0) return false;
128
+ return JSON.stringify(row.missing) === JSON.stringify(expectedMissing);
129
+ }
130
+
131
+ function isDevSessionContractNoise(row) {
132
+ return isBareGoalConstructViolation(row)
133
+ || isEmptyProbeViolation(row, 'researcher-to-architect', RESEARCHER_PROBE_MISSING)
134
+ || isEmptyProbeViolation(row, 'accessibility-to-qa', A11Y_PROBE_MISSING);
135
+ }
136
+
110
137
  /**
111
138
  * @param {string} projectDir
112
139
  * @returns {{ shouldSupersede: boolean, recentCount: number, reason: string }}
@@ -116,18 +143,18 @@ export function planContractViolationSupersede(projectDir) {
116
143
  if (!recent.length) {
117
144
  return { shouldSupersede: false, recentCount: 0, reason: 'no recent contract violations' };
118
145
  }
119
- const allBareGoal = recent.every(isBareGoalConstructViolation);
120
- if (!allBareGoal) {
146
+ const allDevSessionNoise = recent.every(isDevSessionContractNoise);
147
+ if (!allDevSessionNoise) {
121
148
  return {
122
149
  shouldSupersede: false,
123
150
  recentCount: recent.length,
124
- reason: 'recent violations include non-bare-goal construct-to-orchestrator or other contracts',
151
+ reason: 'recent violations include non-probe contract failures',
125
152
  };
126
153
  }
127
154
  return {
128
155
  shouldSupersede: true,
129
156
  recentCount: recent.length,
130
- reason: 'historical bare-goal construct-to-orchestrator violations superseded after handoff enrichment fix',
157
+ reason: 'dev-session contract probes superseded (bare-goal construct, empty researcher/a11y packets)',
131
158
  };
132
159
  }
133
160
 
@@ -0,0 +1,53 @@
1
+ /**
2
+ * lib/oracle/remediation-dispatch.mjs — static vs swarm dispatch for Oracle remediation.
3
+ *
4
+ * Seeds orchestration-policy teamRouting with gap/action remediation specialists.
5
+ * Swarm when involvedTeams.length > 1; static when a single team owns the work.
6
+ */
7
+
8
+ import { classifyIntent, teamRoutingForSpecialists } from '../orchestration-policy.mjs';
9
+ import { routeGap, routeAction } from './routing.mjs';
10
+
11
+ function remediationText(item) {
12
+ return String(item.detail ?? item.summary ?? item.id ?? 'Oracle remediation');
13
+ }
14
+
15
+ function seededSpecialists(item) {
16
+ const route = item.remediationRoute ?? (item.kind ? routeAction(item.kind) : routeGap(item));
17
+ return [route.primary, route.secondary].filter(Boolean);
18
+ }
19
+
20
+ /**
21
+ * @param {object} item — gap or recommended action with optional remediationRoute
22
+ * @param {{ projectDir?: string, cwd?: string }} [opts]
23
+ * @returns {{ mode: 'static'|'swarm', primary: string, specialists: string[], teamRouting: object }}
24
+ */
25
+ export function resolveRemediationDispatch(item, { projectDir, cwd } = {}) {
26
+ const route = item.remediationRoute ?? (item.kind ? routeAction(item.kind) : routeGap(item));
27
+ const primary = route.primary;
28
+ const specialists = seededSpecialists(item);
29
+ const request = remediationText(item);
30
+ const intent = classifyIntent(request);
31
+ const teamRouting = teamRoutingForSpecialists(specialists, {
32
+ intent,
33
+ request,
34
+ cwd: cwd ?? projectDir ?? null,
35
+ });
36
+
37
+ const involvedTeams = teamRouting.involvedTeams ?? [];
38
+ if (involvedTeams.length <= 1) {
39
+ return {
40
+ mode: 'static',
41
+ primary,
42
+ specialists: [primary],
43
+ teamRouting,
44
+ };
45
+ }
46
+
47
+ return {
48
+ mode: 'swarm',
49
+ primary,
50
+ specialists: Array.from(new Set(specialists)),
51
+ teamRouting,
52
+ };
53
+ }