@geraldmaron/construct 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +55 -38
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +261 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +103 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,418 @@
1
+ /**
2
+ * lib/orchestration/flow-selection.mjs — specialist/flow selection for
3
+ * orchestration routing policy: which specialists run, in what order,
4
+ * parallel checks, proactive triggers, and the top-level routeRequest/
5
+ * routeRequestVerified/buildConstructToOrchestratorPacket entry points.
6
+ *
7
+ * Extracted from lib/orchestration-policy.mjs (construct-rf26.10). This is
8
+ * the module that composes classification.mjs (what kind of request) and
9
+ * gates.mjs (what must be true first) into a concrete dispatch plan.
10
+ * orchestration-policy.mjs re-exports these unchanged for existing callers.
11
+ */
12
+ import { resolveContractChain } from '../specialist-contracts.mjs';
13
+ import { verifyRoute } from '../intent-classifier.mjs';
14
+ import { evaluateWatchConditions } from './routing-tables.mjs';
15
+ import { resolveArtifactWorkflowContract } from '../artifact-manifest.mjs';
16
+ import { buildResearchExecutionPolicy } from '../research-execution-policy.mjs';
17
+ import { EXECUTION_TRACKS, INTENT_CLASSES, WORK_CATEGORIES } from './policy-constants.mjs';
18
+ import {
19
+ classifyEngineerFlavor, isExplorerRequest, isProductIntelligenceRequest,
20
+ isDataAnalysisRequest, isDataEngineeringRequest, detectDocAuthoringIntent,
21
+ isLegalComplianceRequest, isBusinessStrategyRequest, isOperationsPlanningRequest,
22
+ isRdLeadRequest, containsAny, detectRiskFlags, classifyWorkCategory,
23
+ classifyIntent, classifyRoleFlavors, classifyProductManagerFlavor,
24
+ isVisualDeliverableRequest, determineExecutionTrack,
25
+ } from './classification.mjs';
26
+ import {
27
+ requiresExternalResearch, requiresFramingChallenge,
28
+ resolveArtifactReviewRequirements, teamRoutingForSpecialists,
29
+ } from './gates.mjs';
30
+
31
+ const SPECIALIST_MAP = {
32
+ implementation: ['cx-engineer'],
33
+ investigation: ['cx-debugger', 'cx-engineer'],
34
+ evaluation: ['cx-reviewer'],
35
+ fix: ['cx-debugger', 'cx-engineer'],
36
+ research: ['cx-researcher'],
37
+ };
38
+
39
+ const WORKFLOW_SKILL_TO_TYPE = {
40
+ 'docs/prd-workflow': 'prd-draft',
41
+ 'docs/adr-workflow': 'architecture-review',
42
+ 'docs/research-workflow': 'research-synthesis',
43
+ 'docs/evidence-ingest-workflow': 'evidence-ingest',
44
+ };
45
+
46
+ // AI/platform/data engineering are skill bundles on the single cx-engineer
47
+ // specialist, not separate dispatch targets, so specialist selection never
48
+ // substitutes a flavor-specific id here. The engineer flavor is still
49
+ // detected via classifyEngineerFlavor()/classifyRoleFlavors() and drives
50
+ // which skills/roles/*.md overlay loads onto cx-engineer — see
51
+ // lib/roles/flavor-bindings.mjs for that binding.
52
+ function refineEngineeringSpecialists(specialists) {
53
+ return specialists;
54
+ }
55
+
56
+ function suggestWorkflowType({ intent, request = '', docAuthoring } = {}) {
57
+ const text = String(request).toLowerCase();
58
+ const localWalkthrough = isExplorerRequest(request)
59
+ || /\b(?:explain|understand|what does|what is)\b[\s\S]{0,60}\b(?:how\b|do\b|does\b|works?\b|flow\b|path\b|layer\b|module\b|implementation\b|code\b|function\b)/i.test(text)
60
+ || /\bhow\b[\s\S]{0,40}\bworks?\b/i.test(text);
61
+ if (intent === INTENT_CLASSES.research && !localWalkthrough) {
62
+ return 'research-synthesis';
63
+ }
64
+ if (docAuthoring?.docType) {
65
+ const contract = resolveArtifactWorkflowContract(docAuthoring.docType);
66
+ const workflowType = WORKFLOW_SKILL_TO_TYPE[contract?.workflowSkill];
67
+ if (workflowType) return workflowType;
68
+ }
69
+ return null;
70
+ }
71
+
72
+ export function selectSpecialists({ request = '', intent, track, riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
73
+ const text = String(request).toLowerCase();
74
+ const productRequest = isProductIntelligenceRequest(text);
75
+ const dataAnalysisRequest = isDataAnalysisRequest(text);
76
+ const dataEngineeringRequest = isDataEngineeringRequest(text);
77
+ if (track === EXECUTION_TRACKS.immediate) return [];
78
+ if (track === EXECUTION_TRACKS.focused) {
79
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) return ['cx-designer'];
80
+ if (productRequest) return ['cx-product-manager'];
81
+ const docAuthoring = detectDocAuthoringIntent(text);
82
+ if (docAuthoring?.owner) return [docAuthoring.owner];
83
+ if (dataEngineeringRequest) return ['cx-engineer'];
84
+ if (dataAnalysisRequest) return ['cx-data-analyst'];
85
+ if (isLegalComplianceRequest(text)) return ['cx-security'];
86
+ if (isBusinessStrategyRequest(text)) return ['cx-product-manager'];
87
+ if (isOperationsPlanningRequest(text)) return ['cx-operations'];
88
+ if (isRdLeadRequest(text)) return ['cx-architect'];
89
+ if (isExplorerRequest(text)) return ['cx-researcher'];
90
+ if (riskFlags.docs) return ['cx-operations'];
91
+ if (riskFlags.security && intent === INTENT_CLASSES.evaluation) return refineEngineeringSpecialists(['cx-security'], request);
92
+ return refineEngineeringSpecialists(SPECIALIST_MAP[intent] || ['cx-engineer'], request);
93
+ }
94
+
95
+ const specialists = ['cx-architect'];
96
+ if (intent === INTENT_CLASSES.fix || intent === INTENT_CLASSES.investigation) specialists.push('cx-debugger');
97
+ if (intent === INTENT_CLASSES.research) specialists.push('cx-researcher');
98
+ const docAuthoring = detectDocAuthoringIntent(text);
99
+ if (docAuthoring?.owner) specialists.push(docAuthoring.owner);
100
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) specialists.push('cx-designer');
101
+ if (productRequest) specialists.push('cx-product-manager');
102
+ if (dataAnalysisRequest) specialists.push('cx-data-analyst');
103
+ if (dataEngineeringRequest) specialists.push('cx-engineer');
104
+ else if (riskFlags.docs) specialists.push('cx-operations');
105
+ specialists.push('cx-engineer', 'cx-reviewer', 'cx-qa');
106
+ if (riskFlags.security || riskFlags.dataIntegrity) specialists.push('cx-security');
107
+ return refineEngineeringSpecialists(Array.from(new Set(specialists)), request);
108
+ }
109
+
110
+ export function augmentSpecialists(
111
+ specialists = [],
112
+ {
113
+ request = '',
114
+ docAuthoring = null,
115
+ externalResearch = null,
116
+ framingChallenge = null,
117
+ artifactReview = null,
118
+ triggers = [],
119
+ } = {},
120
+ ) {
121
+ let list = [...specialists];
122
+ if (docAuthoring?.owner && !list.includes(docAuthoring.owner)) {
123
+ list = [docAuthoring.owner, ...list];
124
+ }
125
+ if (externalResearch?.required && !list.includes('cx-researcher')) {
126
+ list = ['cx-researcher', ...list];
127
+ }
128
+ if (framingChallenge?.required && !list.includes('cx-reviewer')) {
129
+ list = ['cx-reviewer', ...list];
130
+ }
131
+ for (const reviewer of artifactReview?.requiredReviewers || []) {
132
+ if (!list.includes(reviewer)) list = [...list, reviewer];
133
+ }
134
+ if (isLegalComplianceRequest(request) && !list.includes('cx-security')) {
135
+ list = ['cx-security', ...list];
136
+ }
137
+ if (isRdLeadRequest(request) && !list.includes('cx-architect')) {
138
+ list = ['cx-architect', ...list];
139
+ }
140
+ if (isBusinessStrategyRequest(request) && !list.includes('cx-product-manager')) {
141
+ list = ['cx-product-manager', ...list];
142
+ }
143
+ for (const { specialist } of triggers) {
144
+ if (!list.includes(specialist)) list = [specialist, ...list];
145
+ }
146
+ return Array.from(new Set(list));
147
+ }
148
+
149
+ function enrichPolicyForProjectQuestion(request, policySpecialists = []) {
150
+ if (!/\b(what is this (project|repo|codebase)|what('s| is) this (project|repo|codebase)|describe this (project|repo|codebase))\b/i.test(String(request))) {
151
+ return policySpecialists;
152
+ }
153
+ const list = [...policySpecialists];
154
+ if (!list.includes('cx-researcher')) list.unshift('cx-researcher');
155
+ return Array.from(new Set(list));
156
+ }
157
+
158
+ /**
159
+ * Identify specialists that can run in parallel with implementation.
160
+ * Returns an array of specialist names that should execute concurrently
161
+ * rather than sequentially. This reduces latency for independent checks.
162
+ *
163
+ * Parallel execution is safe when:
164
+ * - The specialist only reads/analyzes (doesn't mutate)
165
+ * - Their output doesn't depend on implementation completing first
166
+ * - They provide early feedback that can shape implementation
167
+ */
168
+ export function identifyParallelChecks({ request = '', riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
169
+ const parallelChecks = [];
170
+ const text = String(request).toLowerCase();
171
+
172
+ // Security review can run parallel to implementation for early threat detection
173
+ if (riskFlags.security || containsAny(text, ['auth', 'permission', 'secret', 'payment', 'pii'])) {
174
+ parallelChecks.push('cx-security');
175
+ }
176
+
177
+ // Accessibility review can run parallel for UI work
178
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui || containsAny(text, ['ui', 'ux', 'interface', 'component'])) {
179
+ parallelChecks.push('cx-designer');
180
+ }
181
+
182
+ // Performance review for data-intensive operations
183
+ if (containsAny(text, ['performance', 'latency', 'throughput', 'scale', 'optimization'])) {
184
+ parallelChecks.push('cx-operations');
185
+ }
186
+
187
+ // Legal compliance for regulated domains — same specialist as the security
188
+ // check above (legal-compliance folded into cx-security), so dedupe.
189
+ if (isLegalComplianceRequest(request) && !parallelChecks.includes('cx-security')) {
190
+ parallelChecks.push('cx-security');
191
+ }
192
+
193
+
194
+ return parallelChecks;
195
+ }
196
+
197
+ // Structured request signals computed once and fed to both selectSpecialists
198
+ // and the proactive-trigger pass. Centralises every cheap signal so specialists
199
+ // don't each re-derive them from raw text. Returned shape is stable — additive
200
+ // only.
201
+
202
+ export function requestSignals(request = '', context = {}) {
203
+ const text = String(request).toLowerCase();
204
+ const riskFlags = detectRiskFlags(request);
205
+ const intent = classifyIntent(request);
206
+ const workCategory = classifyWorkCategory(request, riskFlags);
207
+
208
+ const ambiguityIndicators = [
209
+ 'maybe', 'something like', 'figure out', 'somehow', 'sort of', 'kind of', 'idea', 'rough', 'tbd',
210
+ 'we should probably', 'not sure', 'thinking about',
211
+ ];
212
+ const namedConstraintIndicators = [
213
+ 'must not', 'cannot exceed', 'has to', 'deadline', 'budget', 'within', 'no later than',
214
+ 'maximum', 'minimum', 'sla', 'p95', 'p99',
215
+ ];
216
+ const blastRadiusWide = [
217
+ 'all users', 'every user', 'global', 'org-wide', 'company-wide', 'breaking change',
218
+ 'migration', 'mass update', 'backfill', 'destructive',
219
+ ];
220
+ const blastRadiusMedium = [
221
+ 'feature flag', 'beta cohort', 'experiment', 'rollout', 'shadow',
222
+ ];
223
+
224
+ const ambiguityHits = ambiguityIndicators.filter((kw) => text.includes(kw)).length;
225
+ const ambiguityScore = Math.min(1, ambiguityHits / 3);
226
+
227
+ let blastRadius = 'narrow';
228
+ if (containsAny(text, blastRadiusWide)) blastRadius = 'wide';
229
+ else if (containsAny(text, blastRadiusMedium)) blastRadius = 'medium';
230
+
231
+ return {
232
+ intent,
233
+ workCategory,
234
+ riskFlags,
235
+ ambiguityScore,
236
+ hasSuccessMetric: containsAny(text, ['success metric', 'kpi', 'target metric', 'goal metric', 'acceptance criteria']),
237
+ hasNamedConstraints: containsAny(text, namedConstraintIndicators),
238
+ blastRadius,
239
+ authOrPayments: containsAny(text, ['auth', 'authentication', 'authorization', 'payment', 'pii', 'personal data', 'compliance']),
240
+ visualDeliverable: isVisualDeliverableRequest(text),
241
+ namedUsers: context?.namedUsers || [],
242
+ };
243
+ }
244
+
245
+ // Signal-driven proactive triggers. Returns a list of { specialist, reason }
246
+ // pairs for specialists that should engage PRE-DISPATCH based on signals —
247
+ // separate from the keyword-only paths in selectSpecialists. The watch
248
+ // predicates and their specialist owners are declared in
249
+ // specialists/org (watchConditions) and evaluated by
250
+ // orchestration/routing-tables.mjs.
251
+
252
+ export function proactiveTriggers(signals) {
253
+ return evaluateWatchConditions(signals).map(({ specialist, reason }) => ({ specialist, reason }));
254
+ }
255
+
256
+ // Format active flavor overlays into a one-line trace, e.g.
257
+ // "Overlays: architect=platform (matched kubernetes, infra), engineer=ai (matched llm, agent)"
258
+
259
+ export function formatOverlayTrace(roleFlavors = {}, request = '') {
260
+ const text = String(request).toLowerCase();
261
+ const active = Object.entries(roleFlavors).filter(([, flavor]) => flavor);
262
+ if (active.length === 0) return '';
263
+ const FLAVOR_KEYWORDS = {
264
+ architect: ['agent', 'rag', 'retrieval', 'embedding', 'integration', 'webhook', 'warehouse', 'schema', 'sso', 'rbac', 'platform', 'kubernetes', 'infra'],
265
+ productManager: ['platform', 'api', 'enterprise', 'ai product', 'agent', 'growth', 'activation'],
266
+ qa: ['agent', 'prompt', 'api', 'sdk', 'pipeline', 'etl', 'ui', 'ux', 'accessibility'],
267
+ security: ['prompt injection', 'privacy', 'pii', 'dependency', 'cloud', 'iam', 'auth', 'xss', 'csrf'],
268
+ dataAnalyst: ['experiment', 'a/b', 'telemetry', 'metric', 'funnel'],
269
+ dataEngineer: ['vector', 'embedding', 'warehouse', 'pipeline', 'etl', 'pgvector'],
270
+ engineer: ['llm', 'agent', 'rag', 'kubernetes', 'k8s', 'terraform', 'infra', 'docker', 'pipeline', 'etl', 'data model'],
271
+ };
272
+ const parts = active.map(([role, flavor]) => {
273
+ const kws = (FLAVOR_KEYWORDS[role] || []).filter((kw) => text.includes(kw));
274
+ const matched = kws.slice(0, 3).join(', ');
275
+ return matched ? `${role}=${flavor} (matched ${matched})` : `${role}=${flavor}`;
276
+ });
277
+ return `Overlays: ${parts.join(', ')}`;
278
+ }
279
+
280
+ export function routeRequest(options = {}) {
281
+ const intent = classifyIntent(options.request);
282
+ const riskFlags = detectRiskFlags(options.request);
283
+ const roleFlavors = classifyRoleFlavors(options.request);
284
+ const productFlavor = isProductIntelligenceRequest(options.request)
285
+ ? classifyProductManagerFlavor(options.request)
286
+ : null;
287
+ const workCategory = classifyWorkCategory(options.request, riskFlags);
288
+ const track = determineExecutionTrack({ ...options, riskFlags });
289
+ const docAuthoring = detectDocAuthoringIntent(options.request);
290
+ const externalResearch = requiresExternalResearch({ request: options.request, workCategory, riskFlags });
291
+ const framingChallenge = requiresFramingChallenge({ request: options.request, workCategory, riskFlags, introducesContract: options.introducesContract });
292
+ const artifactReview = resolveArtifactReviewRequirements(docAuthoring);
293
+ const suggestedWorkflowType = suggestWorkflowType({ intent, request: options.request, docAuthoring });
294
+ const researchExecutionPolicy = intent === INTENT_CLASSES.research || externalResearch?.required
295
+ ? buildResearchExecutionPolicy({ request: options.request })
296
+ : null;
297
+ const signals = requestSignals(options.request, options.context);
298
+ const triggers = proactiveTriggers(signals);
299
+ const reasons = {};
300
+ for (const { specialist, reason } of triggers) {
301
+ reasons[specialist] = reason;
302
+ }
303
+
304
+ const augmentOpts = {
305
+ request: options.request,
306
+ docAuthoring,
307
+ externalResearch,
308
+ framingChallenge,
309
+ artifactReview,
310
+ triggers,
311
+ };
312
+
313
+ let specialists = augmentSpecialists(
314
+ selectSpecialists({ ...options, intent, track, riskFlags, workCategory }),
315
+ augmentOpts,
316
+ );
317
+
318
+ const policyTrack = track === EXECUTION_TRACKS.immediate ? EXECUTION_TRACKS.focused : track;
319
+ let policySpecialists = augmentSpecialists(
320
+ selectSpecialists({ ...options, intent, track: policyTrack, riskFlags, workCategory }),
321
+ augmentOpts,
322
+ );
323
+ policySpecialists = enrichPolicyForProjectQuestion(options.request, policySpecialists);
324
+
325
+ const displaySpecialists = specialists.length ? specialists : policySpecialists;
326
+
327
+ const contractChain = resolveContractChain({
328
+ intent,
329
+ workCategory,
330
+ track,
331
+ riskFlags,
332
+ specialists: displaySpecialists,
333
+ framingChallenge,
334
+ externalResearch,
335
+ docAuthoring,
336
+ artifactReview,
337
+ });
338
+
339
+ // Team-aware routing: analyze specialist-to-team mapping and identify
340
+ // team involvement, primary owner, required approvals, and escalation paths.
341
+ const teamRouting = teamRoutingForSpecialists(displaySpecialists, {
342
+ intent,
343
+ request: options.request,
344
+ cwd: options.cwd ?? null,
345
+ docAuthoring,
346
+ });
347
+
348
+ const route = {
349
+ intent,
350
+ workCategory,
351
+ track,
352
+ riskFlags,
353
+ productFlavor,
354
+ roleFlavors,
355
+ specialists,
356
+ policySpecialists,
357
+ displaySpecialists,
358
+ signals,
359
+ triggers,
360
+ dispatchReasons: reasons,
361
+ docAuthoring,
362
+ externalResearch,
363
+ framingChallenge,
364
+ artifactReview,
365
+ suggestedWorkflowType,
366
+ researchExecutionPolicy,
367
+ contractChain,
368
+ teamRouting,
369
+ };
370
+
371
+ if (process.env.CONSTRUCT_VERBOSE === '1' && triggers.length > 0) {
372
+ for (const { specialist, reason } of triggers) {
373
+ console.error(`[route] proactive: ${specialist} (${reason})`);
374
+ }
375
+ }
376
+
377
+ return route;
378
+ }
379
+
380
+ // Sync wrapper that returns the keyword route immediately and fires the
381
+ // LLM intent verifier in the background. The verifier writes its verdict
382
+ // to ~/.cx/intent-verifications.jsonl for offline tuning; the dispatched
383
+ // route never waits on a model round-trip. Disable the background call
384
+ // entirely with CONSTRUCT_INTENT_VERIFY=off — see lib/intent-classifier.mjs.
385
+ export function routeRequestVerified(options = {}) {
386
+ const route = routeRequest(options);
387
+ return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
388
+ }
389
+
390
+ /**
391
+ * Build the construct→orchestrator contract packet (specialists/org).
392
+ * Callers must pass this object to agent_contract — not a bare goal string.
393
+ */
394
+ export function buildConstructToOrchestratorPacket(options = {}) {
395
+ const request = String(options.request || '');
396
+ const route = options.route || routeRequest({
397
+ request,
398
+ fileCount: options.fileCount ?? 0,
399
+ moduleCount: options.moduleCount ?? 0,
400
+ introducesContract: options.introducesContract ?? false,
401
+ explicitDrive: options.explicitDrive ?? false,
402
+ });
403
+ if (route.track === EXECUTION_TRACKS.immediate) return null;
404
+
405
+ const goal = String(options.goal || request || '').trim() || 'User-requested work';
406
+ const acceptanceCriteria = Array.isArray(options.acceptanceCriteria) && options.acceptanceCriteria.length
407
+ ? options.acceptanceCriteria
408
+ : ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
409
+
410
+ return {
411
+ goal,
412
+ intent: route.intent,
413
+ workCategory: route.workCategory,
414
+ riskFlags: route.riskFlags || {},
415
+ suggestedWorkflowType: route.suggestedWorkflowType || null,
416
+ acceptanceCriteria,
417
+ };
418
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * lib/orchestration/gates.mjs — gate evaluation for orchestration routing
3
+ * policy: research/framing/executive-approval gates, artifact review
4
+ * requirements, and team-aware routing (blocked-decision escalation).
5
+ *
6
+ * Extracted from lib/orchestration-policy.mjs (construct-rf26.10). Every
7
+ * function here answers "must this be true before work starts / who must
8
+ * approve" — as distinct from classification.mjs (what kind of request is
9
+ * this) and flow-selection.mjs (which specialists/chain run it).
10
+ * orchestration-policy.mjs re-exports these unchanged for existing callers.
11
+ */
12
+ import { getArtifactEntry } from '../artifact-manifest.mjs';
13
+ import { loadRegistry } from '../registry/loader.mjs';
14
+ import { resolveActiveScope } from '../scopes/loader.mjs';
15
+ import { scopeTeamsById, resolveIntentTeamForScope, resolveScopeTeamMeta } from '../scopes/teams.mjs';
16
+ import { WORK_CATEGORIES, INTENT_TO_TEAM } from './policy-constants.mjs';
17
+ import {
18
+ classifyWorkCategory, detectRiskFlags, requiresLiveWebAccess,
19
+ extractNamedEntities, classifyResearchShape, isProductIntelligenceRequest,
20
+ detectDocAuthoringIntent,
21
+ } from './classification.mjs';
22
+
23
+ /**
24
+ * Returns whether external research is required before scaffolding, with the
25
+ * reason. Triggered by named entities not in the project glossary, by
26
+ * architecture / writing / docs work, or by research-shaped intent regardless of
27
+ * entities.
28
+ */
29
+ export function requiresExternalResearch({ request = '', workCategory, riskFlags } = {}) {
30
+ const entities = extractNamedEntities(request);
31
+ const category = workCategory ?? classifyWorkCategory(request);
32
+ const flags = riskFlags ?? detectRiskFlags(request);
33
+ if (requiresLiveWebAccess(request)) {
34
+ return { required: true, reason: 'web-access' };
35
+ }
36
+ if (entities.length > 0) {
37
+ return { required: true, reason: 'named-entities', entities };
38
+ }
39
+ if (category === WORK_CATEGORIES.writing || flags.architecture || flags.docs) {
40
+ return { required: true, reason: 'writing-or-architecture' };
41
+ }
42
+ // Research-shaped intent (compare, landscape, market, standards, …) fires even
43
+ // without a named entity, since the answer is external. Bare research intent
44
+ // does not: a code walkthrough ("explain how X works", "understand the
45
+ // retrieval path") carries none of these terms and stays answered from local
46
+ // context — the distinction that keeps the gate from firing on every prompt.
47
+ const shape = classifyResearchShape(request);
48
+ if (shape) {
49
+ return { required: true, reason: 'research-shaped', shape };
50
+ }
51
+ return { required: false };
52
+ }
53
+
54
+ /**
55
+ * Returns whether the request must pass a framing challenge
56
+ * (cx-reviewer's plan-challenge mode or cx-architect problem-reframing) before scaffolding.
57
+ * Fires for architecture work, documentation sets, and any research-driven
58
+ * artifact. Fail-closed: when in doubt, require the challenge.
59
+ */
60
+ export function requiresFramingChallenge({ request = '', workCategory, riskFlags, introducesContract = false } = {}) {
61
+ const category = workCategory ?? classifyWorkCategory(request);
62
+ const flags = riskFlags ?? detectRiskFlags(request);
63
+ if (flags.architecture || introducesContract) return { required: true, reason: 'architecture-or-contract' };
64
+ if (category === WORK_CATEGORIES.writing && flags.docs) return { required: true, reason: 'documentation-set' };
65
+ if (isProductIntelligenceRequest(request)) return { required: true, reason: 'product-intelligence' };
66
+ if (detectDocAuthoringIntent(request)) return { required: true, reason: 'typed-document' };
67
+ return { required: false };
68
+ }
69
+
70
+ export function requiresExecutiveApproval({
71
+ scopeChange = false,
72
+ productDecision = false,
73
+ riskAcceptance = false,
74
+ irreversibleAction = false,
75
+ blockedDependency = false,
76
+ } = {}) {
77
+ return Boolean(scopeChange || productDecision || riskAcceptance || irreversibleAction || blockedDependency);
78
+ }
79
+
80
+ export function resolveArtifactReviewRequirements(docAuthoring) {
81
+ if (!docAuthoring?.docType) {
82
+ return { requiredReviewers: [], optionalReviewers: [], releaseGate: null };
83
+ }
84
+ const entry = getArtifactEntry(docAuthoring.docType);
85
+ const gate = entry?.releaseGate ?? {};
86
+ return {
87
+ requiredReviewers: gate.requiredReviewers ?? [],
88
+ optionalReviewers: gate.optionalReviewers ?? [],
89
+ releaseGate: gate,
90
+ };
91
+ }
92
+
93
+ // Map a free-text request to the decision names that gate it. Keyed against the
94
+ // union of every team's forbiddenDecisions so a BLOCKED status fires only when
95
+ // the request actually asks for a decision the primary team cannot make. Ordered
96
+ // most-specific-first (deployment-timing before deployment) so the narrow match
97
+ // wins.
98
+
99
+ const REQUESTED_DECISION_PATTERNS = [
100
+ [/\bsecurity[ -]?override\b|\boverride\s+(the\s+)?security\b/i, 'security-override'],
101
+ [/\bdeployment[ -]?timing\b|\bwhen\s+to\s+deploy\b|\bdeploy\s+(?:immediately|now)\b/i, 'deployment-timing'],
102
+ [/\bdeployment[ -]?readiness\b/i, 'deployment-readiness'],
103
+ [/\bdeploy(?:ment|ing|s)?\b|\brelease\s+to\s+prod/i, 'deployment'],
104
+ [/\binfra(?:structure)?[ -]?change\b|\bchange\s+(?:the\s+)?infra/i, 'infra-change'],
105
+ [/\bscope[ -]?change\b|\bchange\s+(?:the\s+)?scope\b|\bre-?scope\b/i, 'scope-change'],
106
+ [/\bproduct[ -]?scope\b/i, 'product-scope'],
107
+ [/\buser[ -]?research\s+method/i, 'user-research-methods'],
108
+ [/\buser[ -]?research\b/i, 'user-research'],
109
+ [/\bimplementation[ -]?approach\b/i, 'implementation-approach'],
110
+ [/\bimplementation[ -]?detail/i, 'implementation-details'],
111
+ [/\bsecurity[ -]?policy\b/i, 'security-policy'],
112
+ [/\bops[ -]?procedure|operational\s+procedure/i, 'ops-procedures'],
113
+ [/\barchitect/i, 'architecture'],
114
+ ];
115
+
116
+ function detectRequestedDecisions(request = '') {
117
+ const text = String(request || '');
118
+ const found = new Set();
119
+ for (const [pattern, decision] of REQUESTED_DECISION_PATTERNS) {
120
+ if (pattern.test(text)) found.add(decision);
121
+ }
122
+ return Array.from(found);
123
+ }
124
+
125
+ /**
126
+ * Build team-aware routing metadata (RFC-0004 §2).
127
+ * Returns { primaryTeam, involvedTeams, requiredApprovals, escalationPath, blockedStatus }.
128
+ *
129
+ * The primary team is intent-driven (INTENT_TO_TEAM), falling back to the first
130
+ * specialist's team when the intent has no mapping. involvedTeams unions the
131
+ * primary team with every selected specialist's team. blockedStatus fires when
132
+ * the primary team's forbiddenDecisions intersect the decisions the request asks
133
+ * for; escalation walks the primary team's path first. Team membership and team
134
+ * metadata resolve from specialists/org.
135
+ */
136
+ export function teamRoutingForSpecialists(specialists = [], { intent = null, request = '', cwd = null, docAuthoring = null } = {}) {
137
+ let registry = null;
138
+ try {
139
+ registry = loadRegistry();
140
+ } catch {
141
+ return {
142
+ primaryTeam: null,
143
+ involvedTeams: [],
144
+ requiredApprovals: [],
145
+ escalationPath: [],
146
+ blockedStatus: null,
147
+ };
148
+ }
149
+
150
+ const profile = cwd ? resolveActiveScope(cwd) : null;
151
+ const profileTeams = scopeTeamsById(profile);
152
+
153
+ const teams = new Map();
154
+ const involvedTeamIds = new Set();
155
+ const addTeam = (teamId) => {
156
+ if (!teamId) return;
157
+ involvedTeamIds.add(teamId);
158
+ if (teams.has(teamId)) return;
159
+ const profileTeam = profileTeams ? resolveScopeTeamMeta(teamId, profile) : null;
160
+ teams.set(teamId, profileTeam || registry.teams?.[teamId] || null);
161
+ };
162
+
163
+ for (const specialistId of specialists) {
164
+ const cxId = specialistId.startsWith('cx-') ? specialistId : `cx-${specialistId}`;
165
+ const specialist = registry.specialists?.[cxId];
166
+ if (specialist?.team) addTeam(specialist.team);
167
+ }
168
+
169
+ // RFC-0004 §2 step 2: intent selects the primary owning team. It always counts
170
+ // as involved even if no selected specialist sits on it yet.
171
+ const profileIntentTeam = resolveIntentTeamForScope(intent, profile);
172
+ const intentTeam = profileIntentTeam
173
+ ?? (intent && INTENT_TO_TEAM[intent] ? INTENT_TO_TEAM[intent] : null);
174
+ addTeam(intentTeam);
175
+
176
+ const involvedTeams = Array.from(involvedTeamIds).sort();
177
+ let primaryTeam = intentTeam || (involvedTeams.length > 0 ? involvedTeams[0] : null);
178
+
179
+ if (docAuthoring?.owner) {
180
+ const cxId = docAuthoring.owner.startsWith('cx-') ? docAuthoring.owner : `cx-${docAuthoring.owner}`;
181
+ const ownerTeam = registry.specialists?.[cxId]?.team;
182
+ if (ownerTeam) {
183
+ addTeam(ownerTeam);
184
+ primaryTeam = ownerTeam;
185
+ }
186
+ }
187
+
188
+ // Collect required approvals from involved teams' decision rights.
189
+ const requiredApprovals = new Set();
190
+ for (const teamId of involvedTeamIds) {
191
+ const team = teams.get(teamId);
192
+ if (team?.decisionRights) {
193
+ for (const decision of team.decisionRights) requiredApprovals.add(decision);
194
+ }
195
+ }
196
+
197
+ // RFC-0004 §2 step 5: BLOCKED when the primary team forbids a decision the
198
+ // request actually asks for. Escalate via that team's path.
199
+ let blockedStatus = null;
200
+ const primaryTeamObj = primaryTeam ? (teams.get(primaryTeam) || registry.teams?.[primaryTeam]) : null;
201
+ if (Array.isArray(primaryTeamObj?.forbiddenDecisions)) {
202
+ const blocked = detectRequestedDecisions(request)
203
+ .filter((decision) => primaryTeamObj.forbiddenDecisions.includes(decision));
204
+ if (blocked.length > 0) {
205
+ blockedStatus = {
206
+ team: primaryTeam,
207
+ forbiddenDecisions: blocked,
208
+ escalationPath: primaryTeamObj.escalationPath || [],
209
+ };
210
+ }
211
+ }
212
+
213
+ // Concatenate escalation paths, primary team first, de-duplicated.
214
+ const escalationPath = [];
215
+ const seen = new Set();
216
+ const orderedTeams = primaryTeam
217
+ ? [primaryTeam, ...involvedTeams.filter((t) => t !== primaryTeam)]
218
+ : involvedTeams;
219
+ for (const teamId of orderedTeams) {
220
+ const team = teams.get(teamId) || registry.teams?.[teamId];
221
+ if (Array.isArray(team?.escalationPath)) {
222
+ for (const role of team.escalationPath) {
223
+ if (!seen.has(role)) {
224
+ escalationPath.push(role);
225
+ seen.add(role);
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ const squadId = primaryTeamObj?.kind === 'squad' ? primaryTeam : null;
232
+ const groupId = primaryTeamObj?.kind === 'group' ? primaryTeam : (primaryTeamObj?.groupId ?? null);
233
+
234
+ return {
235
+ primaryTeam,
236
+ squadId,
237
+ groupId,
238
+ collaborators: primaryTeamObj?.collaborators || [],
239
+ involvedTeams,
240
+ requiredApprovals: Array.from(requiredApprovals).sort(),
241
+ escalationPath,
242
+ blockedStatus,
243
+ };
244
+ }