@geraldmaron/construct 1.4.1 → 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 (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -14,1118 +14,74 @@
14
14
  * declaratively on specialist entries in specialists/org and are
15
15
  * resolved by lib/orchestration/routing-tables.mjs. Hardcoded maps here
16
16
  * would create a second source of truth.
17
- */
18
- import { resolveContractChain } from './specialist-contracts.mjs';
19
- import { verifyRoute } from './intent-classifier.mjs';
20
- import { ownerForEvent, ownerForDoc, evaluateWatchConditions, knownDocTypes } from './orchestration/routing-tables.mjs';
21
- import { getArtifactEntry, resolveArtifactWorkflowContract } from './artifact-manifest.mjs';
22
- import { loadRegistry } from './registry/loader.mjs';
23
- import { buildResearchExecutionPolicy } from './research-execution-policy.mjs';
24
- import { resolveActiveScope } from './scopes/loader.mjs';
25
- import { scopeTeamsById, resolveIntentTeamForScope, resolveScopeTeamMeta } from './scopes/teams.mjs';
26
- import { formatOverlayLine } from './roles/flavor-bindings.mjs';
27
-
28
- export const EXECUTION_TRACKS = {
29
- immediate: 'immediate',
30
- focused: 'focused',
31
- orchestrated: 'orchestrated',
32
- };
33
-
34
- export const INTENT_CLASSES = {
35
- research: 'research',
36
- implementation: 'implementation',
37
- investigation: 'investigation',
38
- evaluation: 'evaluation',
39
- fix: 'fix',
40
- };
41
-
42
- // RFC-0004 §2: each intent class maps to the team that naturally owns that work.
43
- // The primary team drives ownership in teamRouting; specialist selection stays
44
- // flavor-driven. Domain refinements the RFC notes (investigation/fix can fall to
45
- // operations-group, research to product-group) layer on as the decision matrix
46
- // grows — the base mapping is deterministic so routing is testable.
47
-
48
- export const INTENT_TO_TEAM = Object.freeze({
49
- research: 'strategy-team',
50
- implementation: 'engineering-team',
51
- investigation: 'engineering-team',
52
- evaluation: 'quality-team',
53
- fix: 'engineering-team',
54
- });
55
-
56
- export const WORK_CATEGORIES = {
57
- visual: 'visual',
58
- deep: 'deep',
59
- quick: 'quick',
60
- writing: 'writing',
61
- analysis: 'analysis',
62
- };
63
-
64
- export const TERMINAL_STATES = ['DONE', 'BLOCKED', 'NEEDS_MAIN_INPUT'];
65
-
66
- // Event and doc-artifact ownership are resolved from specialists/org
67
- // via routing-tables.mjs. Re-exported here so callers that historically
68
- // imported from this module keep working.
69
-
70
- export { ownerForEvent, ownerForDoc } from './orchestration/routing-tables.mjs';
71
-
72
- const DOC_AUTHORING_PATTERNS = [
73
- { pattern: /\b(adr|architecture decision record)s?\b/i, docType: 'adr' },
74
- { pattern: /\bmeta[\s-]prd\b/i, docType: 'meta-prd' },
75
- { pattern: /\bplatform prd\b/i, docType: 'prd-platform' },
76
- { pattern: /\bbusiness prd\b/i, docType: 'prd-business' },
77
- { pattern: /\bprd\b|\bproduct requirements? document/i, docType: 'prd' },
78
- { pattern: /\bprfaq\b|\bpress release.*faq/i, docType: 'prfaq' },
79
- { pattern: /\bone[\s-]pager\b/i, docType: 'one-pager' },
80
- { pattern: /\bbacklog proposal/i, docType: 'backlog-proposal' },
81
- { pattern: /\bcustomer profile/i, docType: 'customer-profile' },
82
- { pattern: /\brfc\b|\brequest for comments?\b/i, docType: 'rfc' },
83
- { pattern: /\barchitecture overview/i, docType: 'architecture-overview' },
84
- { pattern: /\bsystem design/i, docType: 'system-design' },
85
- { pattern: /\bresearch brief/i, docType: 'research-brief' },
86
- { pattern: /\bevidence brief/i, docType: 'evidence-brief' },
87
- { pattern: /\bsignal brief/i, docType: 'signal-brief' },
88
- { pattern: /\bproduct intelligence report/i, docType: 'product-intelligence-report' },
89
- { pattern: /\brunbook/i, docType: 'runbook' },
90
- { pattern: /\bincident report/i, docType: 'incident-report' },
91
- { pattern: /\bpostmortem|\bpost[\s-]mortem/i, docType: 'postmortem' },
92
- { pattern: /\btest plan/i, docType: 'test-plan' },
93
- { pattern: /\bqa strategy/i, docType: 'qa-strategy' },
94
- { pattern: /\bthreat model/i, docType: 'threat-model' },
95
- { pattern: /\bsecurity review/i, docType: 'security-review' },
96
- { pattern: /\bchangelog/i, docType: 'changelog' },
97
- ];
98
-
99
- const AUTHORING_VERBS = /\b(write|writing|draft|drafting|create|creating|author|authoring|produce|producing|compose|composing|prepare|preparing|generate|generating)\b/i;
100
-
101
- /**
102
- * Resolves a document type mentioned in text without requiring an authoring verb.
103
- */
104
- export function resolveDocTypeMention(request = '') {
105
- const text = String(request);
106
- for (const { pattern, docType } of DOC_AUTHORING_PATTERNS) {
107
- if (pattern.test(text)) return docType;
108
- }
109
- return null;
110
- }
111
-
112
- /**
113
- * Detects whether the request is asking for authorship of a typed document
114
- * that has a canonical owner. Returns `{ docType, owner }` when matched,
115
- * or null otherwise.
116
- */
117
- export function detectDocAuthoringIntent(request = '') {
118
- const text = String(request);
119
- if (!AUTHORING_VERBS.test(text)) return null;
120
- for (const { pattern, docType } of DOC_AUTHORING_PATTERNS) {
121
- if (pattern.test(text)) {
122
- return { docType, owner: ownerForDoc(docType) };
123
- }
124
- }
125
- return null;
126
- }
127
-
128
- const PROPER_NOUN_STOPLIST = new Set([
129
- 'I', 'A', 'An', 'The', 'This', 'That', 'These', 'Those', 'My', 'Our', 'Your',
130
- 'We', 'They', 'He', 'She', 'It', 'Please', 'Thanks', 'Hi', 'Hello',
131
- 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
132
- 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
133
- 'September', 'October', 'November', 'December',
134
- 'Construct', 'Claude', 'GPT', 'OpenAI', 'Anthropic', 'GitHub', 'Google', 'Microsoft',
135
- ]);
136
-
137
- /**
138
- * Extracts proper-noun candidates from a request — capitalized tokens that are
139
- * not common English words, day/month names, or known product/company names.
140
- * Detects named entities that likely require external research.
141
- */
142
- export function extractNamedEntities(request = '') {
143
- const text = String(request);
144
- const tokens = new Set();
145
- // CamelCase single words (e.g. "ProjectIverson")
146
- for (const match of text.matchAll(/\b[A-Z][a-z]+[A-Z][A-Za-z]+\b/g)) tokens.add(match[0]);
147
- // Capitalized multi-word sequences (e.g. "Project Iverson")
148
- for (const match of text.matchAll(/\b([A-Z][a-z]{2,})(?:\s+([A-Z][a-z]{2,})){1,4}\b/g)) tokens.add(match[0]);
149
- // Single capitalized words not at sentence start
150
- for (const match of text.matchAll(/(?<=[a-z,;:]\s)[A-Z][a-z]{3,}\b/g)) tokens.add(match[0]);
151
- return Array.from(tokens).filter((token) => {
152
- const head = token.split(/\s+/)[0];
153
- return !PROPER_NOUN_STOPLIST.has(head);
154
- });
155
- }
156
-
157
- // Research-shaped intent is the vocabulary of external and landscape research —
158
- // where the answer lives in papers, vendor docs, standards, or the market, not
159
- // in the local code. Grouped by shape so the matched kind rides on the gate
160
- // result and the decision is traceable. A false positive here is cheap (it only
161
- // offers the cx-researcher specialist, with no routing latency and no heavy-flow
162
- // coupling), so the set is tuned for recall; the precision floor is that a
163
- // code-walkthrough of the user's own system ("explain how X works") carries none
164
- // of these terms and stays answered from local context.
165
- const RESEARCH_SHAPE_PATTERNS = [
166
- ['comparative', [/\bcompare\b/, /\bcomparison\b/, /\bcompared to\b/, /\bversus\b/, /\bhead[\s-]?to[\s-]?head\b/, /\btrade[\s-]?offs?\b/, /\bpros and cons\b/, /\balternatives\b/, /\balternative to\b/]],
167
- ['selection', [/\bbest (approach(es)?|option|tool|library|framework|pattern|way to|method)\b/, /\bwhich\b.{0,40}\b(should|to)\b.{0,15}\b(use|choose|pick|adopt|go with)\b/, /\brecommend(ed|ation)?\b.{0,30}\b(tool|library|framework|approach|stack|way|practice)\b/, /\boptions for\b/]],
168
- ['landscape', [/\blandscape\b/, /\bstate[\s-]of[\s-]the[\s-]art\b/, /\bsurvey of\b/, /\boverview of\b/, /\becosystem\b/, /\bprior art\b/, /\bliterature\b/, /\bexisting (solutions|approaches|tools|work)\b/, /\bwhat'?s out there\b/]],
169
- ['market', [/\bmarket (research|analysis|share|size|landscape|trends?)\b/, /\bcompetitive (analysis|landscape)\b/, /\bcompetitors?\b/, /\bindustry (standard|trends?|benchmarks?)\b/, /\bpricing (comparison|models?|tiers?)\b/, /\badoption (rate|trends?)\b/]],
170
- ['benchmark', [/\bbenchmarks?\b/, /\bbenchmarking\b/, /\bevaluate (options|alternatives|tools|approaches)\b/, /\bperformance comparison\b/]],
171
- ['standards', [/\bbest practices?\b/, /\bindustry standard\b/, /\bconventions? for\b/, /\brecommended (way|approach|practice)\b/, /\brfc\s?\d+\b/, /\bspecification for\b/]],
172
- ];
173
-
174
- /**
175
- * Classify a request's research shape, or null when it carries none. The
176
- * returned category names which kind of external research the prompt implies.
177
- */
178
- export function classifyResearchShape(request = '') {
179
- const text = String(request).toLowerCase();
180
- for (const [category, patterns] of RESEARCH_SHAPE_PATTERNS) {
181
- if (includesAny(text, patterns)) return category;
182
- }
183
- return null;
184
- }
185
-
186
- // Live web access is distinct from research-shape: a bare "connect to the
187
- // internet" or "fetch example.com" carries none of the comparative/landscape
188
- // vocabulary, so without this it falls to the immediate track and the
189
- // orchestrator — which holds no network tools — refuses instead of dispatching
190
- // the web-capable cx-researcher. A literal URL, an explicit online/internet
191
- // phrase, or a fetch/scrape verb paired with a web object is the signal.
192
-
193
- const WEB_VERB = '(?:fetch|download|open|load|read|scrape|crawl|retrieve|pull|grab|access|visit|hit|reach)';
194
- const WEB_OBJECT = '(?:url|link|web\\s?page|web\\s?site|site|page|online|internet|web|endpoint|api)';
195
- const LIVE_WEB_ACCESS_PATTERNS = [
196
- /https?:\/\/\S+/i,
197
- /\bwww\.[a-z0-9-]+\.[a-z]{2,}/i,
198
- /\b(?:connect|connecting|connection)\s+to\s+the\s+(?:internet|web|network)\b/i,
199
- /\binternet\s+(?:access|connection|connectivity)\b/i,
200
- /\b(?:go|get|getting|going)\s+online\b/i,
201
- /\b(?:browse|browsing|surf)\b[\s\S]{0,20}\b(?:web|internet|site|page|url)\b/i,
202
- /\b(?:curl|wget|ping)\b/i,
203
- /\b(?:is|are)\b[\s\S]{0,40}\b(?:online|offline|reachable|unreachable|responding)\b/i,
204
- new RegExp(`\\b${WEB_VERB}\\b[\\s\\S]{0,40}\\b${WEB_OBJECT}\\b`, 'i'),
205
- new RegExp(`\\b${WEB_VERB}\\b[\\s\\S]{0,40}\\b[a-z0-9-]+\\.(?:com|org|net|io|dev|ai|gov|edu|co|app|info|xyz)\\b`, 'i'),
206
- ];
207
-
208
- /**
209
- * Whether a request needs live external web/network access the orchestrator
210
- * cannot perform itself (it is a read-only router with no network tools), so it
211
- * must be routed to cx-researcher rather than answered immediately.
212
- */
213
- export function requiresLiveWebAccess(request = '') {
214
- const text = String(request);
215
- return LIVE_WEB_ACCESS_PATTERNS.some((pattern) => pattern.test(text));
216
- }
217
-
218
- /**
219
- * Returns whether external research is required before scaffolding, with the
220
- * reason. Triggered by named entities not in the project glossary, by
221
- * architecture / writing / docs work, or by research-shaped intent regardless of
222
- * entities.
223
- */
224
- export function requiresExternalResearch({ request = '', workCategory, riskFlags } = {}) {
225
- const entities = extractNamedEntities(request);
226
- const category = workCategory ?? classifyWorkCategory(request);
227
- const flags = riskFlags ?? detectRiskFlags(request);
228
- if (requiresLiveWebAccess(request)) {
229
- return { required: true, reason: 'web-access' };
230
- }
231
- if (entities.length > 0) {
232
- return { required: true, reason: 'named-entities', entities };
233
- }
234
- if (category === WORK_CATEGORIES.writing || flags.architecture || flags.docs) {
235
- return { required: true, reason: 'writing-or-architecture' };
236
- }
237
- // Research-shaped intent (compare, landscape, market, standards, …) fires even
238
- // without a named entity, since the answer is external. Bare research intent
239
- // does not: a code walkthrough ("explain how X works", "understand the
240
- // retrieval path") carries none of these terms and stays answered from local
241
- // context — the distinction that keeps the gate from firing on every prompt.
242
- const shape = classifyResearchShape(request);
243
- if (shape) {
244
- return { required: true, reason: 'research-shaped', shape };
245
- }
246
- return { required: false };
247
- }
248
-
249
- /**
250
- * Returns whether the request must pass a framing challenge
251
- * (cx-devil-advocate or cx-architect problem-reframing) before scaffolding.
252
- * Fires for architecture work, documentation sets, and any research-driven
253
- * artifact. Fail-closed: when in doubt, require the challenge.
254
- */
255
- export function requiresFramingChallenge({ request = '', workCategory, riskFlags, introducesContract = false } = {}) {
256
- const category = workCategory ?? classifyWorkCategory(request);
257
- const flags = riskFlags ?? detectRiskFlags(request);
258
- if (flags.architecture || introducesContract) return { required: true, reason: 'architecture-or-contract' };
259
- if (category === WORK_CATEGORIES.writing && flags.docs) return { required: true, reason: 'documentation-set' };
260
- if (isProductIntelligenceRequest(request)) return { required: true, reason: 'product-intelligence' };
261
- if (detectDocAuthoringIntent(request)) return { required: true, reason: 'typed-document' };
262
- return { required: false };
263
- }
264
-
265
- const SPECIALIST_MAP = {
266
- implementation: ['cx-engineer'],
267
- investigation: ['cx-debugger', 'cx-engineer'],
268
- evaluation: ['cx-reviewer'],
269
- fix: ['cx-debugger', 'cx-engineer'],
270
- research: ['cx-researcher'],
271
- };
272
-
273
- const WORKFLOW_SKILL_TO_TYPE = {
274
- 'docs/prd-workflow': 'prd-draft',
275
- 'docs/adr-workflow': 'architecture-review',
276
- 'docs/research-workflow': 'research-synthesis',
277
- 'docs/evidence-ingest-workflow': 'evidence-ingest',
278
- };
279
-
280
- function includesAny(text, patterns) {
281
- return patterns.some((pattern) => pattern.test(text));
282
- }
283
-
284
- function containsAny(text, words) {
285
- return words.some((word) => text.includes(word));
286
- }
287
-
288
- export function isProductIntelligenceRequest(request = '') {
289
- const text = String(request).toLowerCase();
290
- return containsAny(text, [
291
- 'requirements',
292
- 'prd',
293
- 'prfaq',
294
- 'product brief',
295
- 'signal brief',
296
- 'customer notes',
297
- 'customer profile',
298
- 'product intelligence',
299
- 'backlog proposal',
300
- 'jira proposal',
301
- 'linear proposal',
302
- 'field notes',
303
- 'product spec',
304
- 'meta prd',
305
- ]);
306
- }
307
-
308
- export function classifyProductManagerFlavor(request = '') {
309
- const text = String(request).toLowerCase();
310
- if (containsAny(text, ['platform', 'api', 'sdk', 'developer experience', 'devex', 'integration', 'migration', 'compatibility', 'admin', 'tenant'])) return 'platform';
311
- if (containsAny(text, ['enterprise', 'procurement', 'compliance', 'security review', 'audit', 'sso', 'soc2', 'soc 2', 'rbac'])) return 'enterprise';
312
- if (containsAny(text, ['ai product', 'agent', 'eval', 'evaluation loop', 'model behavior', 'prompt', 'llm', 'human review'])) return 'ai-product';
313
- if (containsAny(text, ['growth', 'activation', 'conversion', 'funnel', 'packaging', 'pricing', 'gtm', 'go-to-market'])) return 'growth';
314
- return 'product';
315
- }
316
-
317
- export function classifyArchitectFlavor(request = '') {
318
- const text = String(request).toLowerCase();
319
- if (containsAny(text, ['agent', 'rag', 'retrieval', 'embedding', 'eval loop', 'model behavior', 'tool use', 'llm'])) return 'ai-systems';
320
- if (containsAny(text, ['integration', 'webhook', 'sync', 'third-party', 'oauth', 'reconciliation', 'idempotency'])) return 'integration';
321
- if (containsAny(text, ['warehouse', 'schema', 'migration', 'retention', 'index', 'backfill', 'data model'])) return 'data';
322
- if (containsAny(text, ['enterprise', 'sso', 'rbac', 'audit', 'data residency', 'procurement', 'tenant isolation'])) return 'enterprise';
323
- if (containsAny(text, ['platform', 'api', 'sdk', 'developer experience', 'devex', 'admin', 'tenant', 'compatibility'])) return 'platform';
324
- return null;
325
- }
326
-
327
- export function classifyQaFlavor(request = '') {
328
- const text = String(request).toLowerCase();
329
- if (containsAny(text, ['agent', 'prompt', 'model', 'eval', 'rag', 'retrieval', 'golden trace'])) return 'ai-eval';
330
- if (containsAny(text, ['api', 'sdk', 'contract', 'status code', 'error body', 'openapi', 'consumer'])) return 'api-contract';
331
- if (containsAny(text, ['pipeline', 'etl', 'elt', 'backfill', 'freshness', 'data quality', 'warehouse'])) return 'data-pipeline';
332
- if (containsAny(text, ['ui', 'ux', 'screen', 'browser', 'playwright', 'responsive', 'keyboard', 'accessibility', 'visual'])) return 'web-ui';
333
- return null;
334
- }
335
-
336
- export function classifySecurityFlavor(request = '') {
337
- const text = String(request).toLowerCase();
338
- if (containsAny(text, ['prompt injection', 'agent', 'rag', 'retrieval', 'embedding', 'model', 'tool scoping'])) return 'ai';
339
- if (containsAny(text, ['privacy', 'pii', 'retention', 'deletion', 'consent', 'telemetry', 'trace', 'export'])) return 'privacy';
340
- if (containsAny(text, ['dependency', 'package', 'supply chain', 'sbom', 'provenance', 'ci permission', 'signing'])) return 'supply-chain';
341
- if (containsAny(text, ['cloud', 'iam', 'bucket', 'network policy', 'encryption', 'public access', 'drift'])) return 'cloud';
342
- if (containsAny(text, ['auth', 'authorization', 'xss', 'csrf', 'ssrf', 'injection', 'input validation', 'jwt'])) return 'appsec';
343
- return null;
344
- }
345
-
346
- export function classifyDataAnalystFlavor(request = '') {
347
- const text = String(request).toLowerCase();
348
- if (containsAny(text, ['customer signal', 'customer notes', 'evidence brief', 'product intelligence', 'field notes', 'signal brief'])) return 'product-intelligence';
349
- if (containsAny(text, ['experiment', 'a/b', 'ab test', 'randomization', 'sample size', 'mde'])) return 'experiment';
350
- if (containsAny(text, ['telemetry', 'trace', 'logs', 'dashboard', 'observability', 'denominator'])) return 'telemetry';
351
- if (containsAny(text, ['metric', 'funnel', 'activation', 'adoption', 'retention', 'conversion', 'guardrail'])) return 'product';
352
- return null;
353
- }
354
-
355
- export function classifyDataEngineerFlavor(request = '') {
356
- const text = String(request).toLowerCase();
357
- if (containsAny(text, ['vector', 'embedding', 'retrieval', 'hybrid search', 'semantic search', 'pgvector'])) return 'vector-retrieval';
358
- if (containsAny(text, ['warehouse', 'metric layer', 'semantic layer', 'dimensional', 'partition', 'incremental model'])) return 'warehouse';
359
- if (containsAny(text, ['pipeline', 'etl', 'elt', 'streaming', 'backfill', 'idempotent', 'data contract'])) return 'pipeline';
360
- return null;
361
- }
362
-
363
- export function classifyEngineerFlavor(request = '') {
364
- const text = String(request).toLowerCase();
365
- if (containsAny(text, ['llm', 'agent', 'prompt', 'eval', 'hallucination', 'rag', 'model behavior', 'model routing'])) return 'ai';
366
- if (containsAny(text, ['vector', 'embedding', 'retrieval', 'warehouse', 'etl', 'elt', 'pipeline', 'streaming', 'data contract'])) return 'data';
367
- if (containsAny(text, ['ci/cd', ' ci ', 'build pipeline', 'build system', 'deploy', 'docker', 'kubernetes', 'terraform', 'helm', 'release pipeline', 'platform tooling', 'developer experience'])) return 'platform';
368
- return null;
369
- }
370
-
371
- export function isDataAnalysisRequest(request = '') {
372
- return Boolean(classifyDataAnalystFlavor(request));
373
- }
374
-
375
- export function isDataEngineeringRequest(request = '') {
376
- return Boolean(classifyDataEngineerFlavor(request));
377
- }
378
-
379
- // Activation detectors for specialists without a flavor classifier. Each maps
380
- // a request to a keyword signature; an empty match leaves the specialist
381
- // dormant unless an event in EVENT_OWNERSHIP fires.
382
-
383
- export function isLegalComplianceRequest(request = '') {
384
- const text = String(request).toLowerCase();
385
- return containsAny(text, [
386
- 'legal review', 'compliance review', 'regulatory', 'gdpr', 'ccpa', 'hipaa',
387
- 'soc 2', 'soc2', 'data processing agreement', 'dpa', 'terms of service',
388
- 'license compliance', 'open-source license', 'attribution requirement',
389
- 'privacy policy', 'consent flow', 'data residency', 'export control',
390
- ]);
391
- }
392
-
393
- export function isBusinessStrategyRequest(request = '') {
394
- const text = String(request).toLowerCase();
395
- return containsAny(text, [
396
- 'go-to-market', 'gtm strategy', 'market positioning', 'competitive analysis',
397
- 'business case', 'value proposition', 'pricing strategy', 'market segmentation',
398
- 'investment thesis', 'strategic direction',
399
- ]);
400
- }
401
-
402
- export function isOperationsPlanningRequest(request = '') {
403
- const text = String(request).toLowerCase();
404
- return containsAny(text, [
405
- 'dependency sequencing', 'critical path', 'milestone plan',
406
- 'resource allocation', 'capacity planning', 'roadmap sequencing',
407
- 'cross-team dependency', 'multi-quarter plan', 'rollout sequencing',
408
- ]);
409
- }
410
-
411
- export function isRdLeadRequest(request = '') {
412
- const text = String(request).toLowerCase();
413
- return containsAny(text, [
414
- 'hypothesis', 'falsifiable', 'research question', 'experimental design',
415
- 'technology spike', 'feasibility study', 'proof of concept', 'r&d',
416
- ]);
417
- }
418
-
419
- export function isExplorerRequest(request = '') {
420
- const text = String(request).toLowerCase();
421
- return containsAny(text, [
422
- 'explore the', 'spike', 'walkthrough', 'code walk', 'scoping pass',
423
- 'recon', 'reconnaissance', 'survey the code', 'orient me',
424
- ]);
425
- }
426
-
427
- export function isVisualDeliverableRequest(request = '') {
428
- const text = String(request).toLowerCase();
429
- return containsAny(text, [
430
- 'wireframe',
431
- 'diagram',
432
- 'flowchart',
433
- 'mermaid',
434
- 'sequence diagram',
435
- 'state diagram',
436
- 'er diagram',
437
- 'mockup',
438
- 'storyboard',
439
- 'deck',
440
- 'slide deck',
441
- 'slides',
442
- 'presentation',
443
- 'powerpoint',
444
- 'ppt',
445
- 'pptx',
446
- 'walkthrough video',
447
- 'demo video',
448
- ]);
449
- }
450
-
451
- export function classifyRoleFlavors(request = '') {
452
- const engineerFlavor = classifyEngineerFlavor(request);
453
- const dataEngineerFlavor = classifyDataEngineerFlavor(request);
454
- return {
455
- engineer: engineerFlavor && !['ai', 'platform', 'data'].includes(engineerFlavor) ? engineerFlavor : null,
456
- aiEngineer: engineerFlavor === 'ai' ? 'core' : null,
457
- platformEngineer: engineerFlavor === 'platform' ? 'core' : null,
458
- architect: classifyArchitectFlavor(request),
459
- productManager: isProductIntelligenceRequest(request) ? classifyProductManagerFlavor(request) : null,
460
- qa: classifyQaFlavor(request),
461
- security: classifySecurityFlavor(request),
462
- dataAnalyst: classifyDataAnalystFlavor(request),
463
- dataEngineer: dataEngineerFlavor,
464
- businessStrategist: isBusinessStrategyRequest(request) ? 'core' : null,
465
- };
466
- }
467
-
468
- // One line per non-null flavor: "cx-<role>: loaded <role>.<flavor>
469
- // overlay". Verbose host surfaces + cx_trace span attribute; lets a
470
- // post-hoc reviewer see which overlays drove a given dispatch.
471
-
472
- export function formatOverlaySelection(roleFlavors) {
473
- if (!roleFlavors || typeof roleFlavors !== 'object') return [];
474
- const lines = [];
475
- for (const [classifierKey, flavor] of Object.entries(roleFlavors)) {
476
- const line = formatOverlayLine(classifierKey, flavor);
477
- if (line) lines.push(line);
478
- }
479
- return lines;
480
- }
481
-
482
- export function detectRiskFlags(request = '') {
483
- const text = String(request).toLowerCase();
484
- return {
485
- architecture: containsAny(text, ['architecture', 'interface contract', 'api contract', 'dependency', 'module boundary', 'data model', 'indexing', 'retrieval design', 'terraform', 'infrastructure', 'iac', 'provisioning', 'blast radius', 'rollout', 'deployment strategy']),
486
- security: containsAny(text, ['security', 'permission', 'secret', 'privacy', 'payment', 'authentication', 'authorization', 'credential', 'oidc', 'iam', 'access token']),
487
- dataIntegrity: containsAny(text, ['migration', 'data', 'sync', 'consistency', 'state', 'tfstate', 'drift']),
488
- ui: containsAny(text, ['ui', 'ux', 'design system', 'screen', 'layout', 'visual', 'onboarding']) && !containsAny(text, ['requirements']),
489
- docs: containsAny(text, ['docs', 'readme', 'runbook', 'adr']),
490
- ai: containsAny(text, ['llm', ' agent', 'prompt', 'rag', 'model behavior', 'retrieval', 'embedding', 'vector']),
491
- };
492
- }
493
-
494
- export function classifyIntent(request = '') {
495
- const text = String(request).toLowerCase();
496
- if (containsAny(text, ['fix', 'bug', 'regression', 'broken', 'repair'])) return INTENT_CLASSES.fix;
497
- if (includesAny(text, [/debug/, /investigate/, /trace/, /root cause/, /why .* fail/])) return INTENT_CLASSES.investigation;
498
- if (isProductIntelligenceRequest(text)) return INTENT_CLASSES.implementation;
499
- if (includesAny(text, [/review/, /audit/, /validate/, /ready to ship/, /check/])) return INTENT_CLASSES.evaluation;
500
- if (includesAny(text, [/research/, /explore/, /compare/, /what does/, /explain/, /understand/, /docs?/])) return INTENT_CLASSES.research;
501
- return INTENT_CLASSES.implementation;
502
- }
503
-
504
- export function classifyWorkCategory(request = '', riskFlags = detectRiskFlags(request)) {
505
- const text = String(request).toLowerCase();
506
- if (riskFlags.ui || isVisualDeliverableRequest(text)) return WORK_CATEGORIES.visual;
507
- if (riskFlags.docs || containsAny(text, ['write', 'rewrite', 'document', 'spec', 'requirements'])) return WORK_CATEGORIES.writing;
508
- if (includesAny(text, [/analy[sz]e/, /measure/, /metrics/, /score/, /evaluate/])) return WORK_CATEGORIES.analysis;
509
- if (riskFlags.architecture || riskFlags.ai || includesAny(text, [/plan/, /strategy/, /system/, /refactor/, /orchestr/])) return WORK_CATEGORIES.deep;
510
- return WORK_CATEGORIES.quick;
511
- }
512
-
513
- export function determineExecutionTrack({
514
- request = '',
515
- fileCount = 0,
516
- moduleCount = 0,
517
- introducesContract = false,
518
- explicitDrive = false,
519
- riskFlags = detectRiskFlags(request),
520
- } = {}) {
521
- const intent = classifyIntent(request);
522
- const workCategory = classifyWorkCategory(request, riskFlags);
523
- const text = String(request).toLowerCase();
524
- const localWalkthrough = isExplorerRequest(request)
525
- || /\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)
526
- || /\bhow\b[\s\S]{0,40}\bworks?\b/i.test(text);
527
- if (explicitDrive) return EXECUTION_TRACKS.orchestrated;
528
- if (intent === INTENT_CLASSES.research) {
529
- if (localWalkthrough && fileCount <= 1 && moduleCount <= 1) return EXECUTION_TRACKS.immediate;
530
- if (fileCount <= 1 && moduleCount <= 1) return EXECUTION_TRACKS.focused;
531
- return EXECUTION_TRACKS.orchestrated;
532
- }
533
- if (introducesContract || fileCount >= 3 || moduleCount >= 2) return EXECUTION_TRACKS.orchestrated;
534
- if (riskFlags.architecture || riskFlags.security || riskFlags.dataIntegrity || riskFlags.ai) return EXECUTION_TRACKS.orchestrated;
535
- if (workCategory === WORK_CATEGORIES.visual) return EXECUTION_TRACKS.focused;
536
- // A keyword that names a specialist should activate that specialist even
537
- // for small-scope work — otherwise the immediate-track fallback below
538
- // silently drops the request to "answer directly," losing the specialty.
539
- if (
540
- isLegalComplianceRequest(request)
541
- || isBusinessStrategyRequest(request)
542
- || isOperationsPlanningRequest(request)
543
- || isRdLeadRequest(request)
544
- || isExplorerRequest(request)
545
- || requiresLiveWebAccess(request)
546
- ) {
547
- return EXECUTION_TRACKS.focused;
548
- }
549
- if (fileCount <= 1 && moduleCount <= 1 && !includesAny(String(request).toLowerCase(), [/end to end/, /ship/, /full/])) return EXECUTION_TRACKS.immediate;
550
- return EXECUTION_TRACKS.focused;
551
- }
552
-
553
- function refineEngineeringSpecialists(specialists, request = '') {
554
- const engineerFlavor = classifyEngineerFlavor(request);
555
- if (!engineerFlavor || !['ai', 'platform', 'data'].includes(engineerFlavor)) return specialists;
556
- const replacement = {
557
- ai: 'cx-ai-engineer',
558
- platform: 'cx-platform-engineer',
559
- data: 'cx-data-engineer',
560
- }[engineerFlavor];
561
- const set = new Set(specialists);
562
- set.delete('cx-engineer');
563
- set.add(replacement);
564
- return [...set];
565
- }
566
-
567
- function suggestWorkflowType({ intent, request = '', docAuthoring } = {}) {
568
- const text = String(request).toLowerCase();
569
- const localWalkthrough = isExplorerRequest(request)
570
- || /\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)
571
- || /\bhow\b[\s\S]{0,40}\bworks?\b/i.test(text);
572
- if (intent === INTENT_CLASSES.research && !localWalkthrough) {
573
- return 'research-synthesis';
574
- }
575
- if (docAuthoring?.docType) {
576
- const contract = resolveArtifactWorkflowContract(docAuthoring.docType);
577
- const workflowType = WORKFLOW_SKILL_TO_TYPE[contract?.workflowSkill];
578
- if (workflowType) return workflowType;
579
- }
580
- return null;
581
- }
582
-
583
- export function selectSpecialists({ request = '', intent, track, riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
584
- const text = String(request).toLowerCase();
585
- const productRequest = isProductIntelligenceRequest(text);
586
- const dataAnalysisRequest = isDataAnalysisRequest(text);
587
- const dataEngineeringRequest = isDataEngineeringRequest(text);
588
- if (track === EXECUTION_TRACKS.immediate) return [];
589
- if (track === EXECUTION_TRACKS.focused) {
590
- if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) return ['cx-designer'];
591
- if (productRequest) return ['cx-product-manager'];
592
- const docAuthoring = detectDocAuthoringIntent(text);
593
- if (docAuthoring?.owner) return [docAuthoring.owner];
594
- if (dataEngineeringRequest) return ['cx-data-engineer'];
595
- if (dataAnalysisRequest) return ['cx-data-analyst'];
596
- if (isLegalComplianceRequest(text)) return ['cx-legal-compliance'];
597
- if (isBusinessStrategyRequest(text)) return ['cx-business-strategist'];
598
- if (isOperationsPlanningRequest(text)) return ['cx-operations'];
599
- if (isRdLeadRequest(text)) return ['cx-rd-lead'];
600
- if (isExplorerRequest(text)) return ['cx-explorer'];
601
- if (riskFlags.docs) return ['cx-docs-keeper'];
602
- if (riskFlags.security && intent === INTENT_CLASSES.evaluation) return refineEngineeringSpecialists(['cx-security'], request);
603
- return refineEngineeringSpecialists(SPECIALIST_MAP[intent] || ['cx-engineer'], request);
604
- }
605
-
606
- const specialists = ['cx-architect'];
607
- if (intent === INTENT_CLASSES.fix || intent === INTENT_CLASSES.investigation) specialists.push('cx-debugger');
608
- if (intent === INTENT_CLASSES.research) specialists.push('cx-researcher');
609
- const docAuthoring = detectDocAuthoringIntent(text);
610
- if (docAuthoring?.owner) specialists.push(docAuthoring.owner);
611
- if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) specialists.push('cx-designer');
612
- if (productRequest) specialists.push('cx-product-manager');
613
- if (dataAnalysisRequest) specialists.push('cx-data-analyst');
614
- if (dataEngineeringRequest) specialists.push('cx-data-engineer');
615
- else if (riskFlags.docs) specialists.push('cx-docs-keeper');
616
- specialists.push('cx-engineer', 'cx-reviewer', 'cx-qa');
617
- if (riskFlags.security || riskFlags.dataIntegrity) specialists.push('cx-security');
618
- return refineEngineeringSpecialists(Array.from(new Set(specialists)), request);
619
- }
620
-
621
- export function augmentSpecialists(
622
- specialists = [],
623
- {
624
- request = '',
625
- docAuthoring = null,
626
- externalResearch = null,
627
- framingChallenge = null,
628
- artifactReview = null,
629
- triggers = [],
630
- } = {},
631
- ) {
632
- let list = [...specialists];
633
- if (docAuthoring?.owner && !list.includes(docAuthoring.owner)) {
634
- list = [docAuthoring.owner, ...list];
635
- }
636
- if (externalResearch?.required && !list.includes('cx-researcher')) {
637
- list = ['cx-researcher', ...list];
638
- }
639
- if (framingChallenge?.required && !list.includes('cx-devil-advocate')) {
640
- list = ['cx-devil-advocate', ...list];
641
- }
642
- for (const reviewer of artifactReview?.requiredReviewers || []) {
643
- if (!list.includes(reviewer)) list = [...list, reviewer];
644
- }
645
- if (isLegalComplianceRequest(request) && !list.includes('cx-legal-compliance')) {
646
- list = ['cx-legal-compliance', ...list];
647
- }
648
- if (isRdLeadRequest(request) && !list.includes('cx-rd-lead')) {
649
- list = ['cx-rd-lead', ...list];
650
- }
651
- if (isBusinessStrategyRequest(request) && !list.includes('cx-business-strategist')) {
652
- list = ['cx-business-strategist', ...list];
653
- }
654
- for (const { specialist } of triggers) {
655
- if (!list.includes(specialist)) list = [specialist, ...list];
656
- }
657
- return Array.from(new Set(list));
658
- }
659
-
660
- function enrichPolicyForProjectQuestion(request, policySpecialists = []) {
661
- 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))) {
662
- return policySpecialists;
663
- }
664
- const list = [...policySpecialists];
665
- if (!list.includes('cx-explorer')) list.unshift('cx-explorer');
666
- if (!list.includes('cx-researcher')) list.splice(list.indexOf('cx-explorer') + 1, 0, 'cx-researcher');
667
- return Array.from(new Set(list));
668
- }
669
-
670
- /**
671
- * Identify specialists that can run in parallel with implementation.
672
- * Returns an array of specialist names that should execute concurrently
673
- * rather than sequentially. This reduces latency for independent checks.
674
- *
675
- * Parallel execution is safe when:
676
- * - The specialist only reads/analyzes (doesn't mutate)
677
- * - Their output doesn't depend on implementation completing first
678
- * - They provide early feedback that can shape implementation
679
- */
680
- export function identifyParallelChecks({ request = '', riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
681
- const parallelChecks = [];
682
- const text = String(request).toLowerCase();
683
-
684
- // Security review can run parallel to implementation for early threat detection
685
- if (riskFlags.security || containsAny(text, ['auth', 'permission', 'secret', 'payment', 'pii'])) {
686
- parallelChecks.push('cx-security');
687
- }
688
-
689
- // Accessibility review can run parallel for UI work
690
- if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui || containsAny(text, ['ui', 'ux', 'interface', 'component'])) {
691
- parallelChecks.push('cx-accessibility');
692
- }
693
-
694
- // Performance review for data-intensive operations
695
- if (containsAny(text, ['performance', 'latency', 'throughput', 'scale', 'optimization'])) {
696
- parallelChecks.push('cx-sre');
697
- }
698
-
699
- // Legal compliance for regulated domains
700
- if (isLegalComplianceRequest(request)) {
701
- parallelChecks.push('cx-legal-compliance');
702
- }
703
-
704
- return parallelChecks;
705
- }
706
-
707
- export function requiresExecutiveApproval({
708
- scopeChange = false,
709
- productDecision = false,
710
- riskAcceptance = false,
711
- irreversibleAction = false,
712
- blockedDependency = false,
713
- } = {}) {
714
- return Boolean(scopeChange || productDecision || riskAcceptance || irreversibleAction || blockedDependency);
715
- }
716
-
717
- // Structured request signals computed once and fed to both selectSpecialists
718
- // and the proactive-trigger pass. Centralises every cheap signal so specialists
719
- // don't each re-derive them from raw text. Returned shape is stable — additive
720
- // only.
721
-
722
- export function requestSignals(request = '', context = {}) {
723
- const text = String(request).toLowerCase();
724
- const riskFlags = detectRiskFlags(request);
725
- const intent = classifyIntent(request);
726
- const workCategory = classifyWorkCategory(request, riskFlags);
727
-
728
- const ambiguityIndicators = [
729
- 'maybe', 'something like', 'figure out', 'somehow', 'sort of', 'kind of', 'idea', 'rough', 'tbd',
730
- 'we should probably', 'not sure', 'thinking about',
731
- ];
732
- const namedConstraintIndicators = [
733
- 'must not', 'cannot exceed', 'has to', 'deadline', 'budget', 'within', 'no later than',
734
- 'maximum', 'minimum', 'sla', 'p95', 'p99',
735
- ];
736
- const blastRadiusWide = [
737
- 'all users', 'every user', 'global', 'org-wide', 'company-wide', 'breaking change',
738
- 'migration', 'mass update', 'backfill', 'destructive',
739
- ];
740
- const blastRadiusMedium = [
741
- 'feature flag', 'beta cohort', 'experiment', 'rollout', 'shadow',
742
- ];
743
-
744
- const ambiguityHits = ambiguityIndicators.filter((kw) => text.includes(kw)).length;
745
- const ambiguityScore = Math.min(1, ambiguityHits / 3);
746
-
747
- let blastRadius = 'narrow';
748
- if (containsAny(text, blastRadiusWide)) blastRadius = 'wide';
749
- else if (containsAny(text, blastRadiusMedium)) blastRadius = 'medium';
750
-
751
- return {
752
- intent,
753
- workCategory,
754
- riskFlags,
755
- ambiguityScore,
756
- hasSuccessMetric: containsAny(text, ['success metric', 'kpi', 'target metric', 'goal metric', 'acceptance criteria']),
757
- hasNamedConstraints: containsAny(text, namedConstraintIndicators),
758
- blastRadius,
759
- authOrPayments: containsAny(text, ['auth', 'authentication', 'authorization', 'payment', 'pii', 'personal data', 'compliance']),
760
- visualDeliverable: isVisualDeliverableRequest(text),
761
- namedUsers: context?.namedUsers || [],
762
- };
763
- }
764
-
765
- // Signal-driven proactive triggers. Returns a list of { specialist, reason }
766
- // pairs for specialists that should engage PRE-DISPATCH based on signals —
767
- // separate from the keyword-only paths in selectSpecialists. The watch
768
- // predicates and their specialist owners are declared in
769
- // specialists/org (watchConditions) and evaluated by
770
- // orchestration/routing-tables.mjs.
771
-
772
- export function proactiveTriggers(signals) {
773
- return evaluateWatchConditions(signals).map(({ specialist, reason }) => ({ specialist, reason }));
774
- }
775
-
776
- // User-visible dispatch summary: one line per specialist with the reason it
777
- // was engaged. Replaces the terse one-line dispatchPlan when callers want to
778
- // surface the routing decision before starting work.
779
-
780
- export function buildDispatchSummary({ specialists = [], reasons = {} } = {}) {
781
- if (specialists.length === 0) return 'Engaging: (none — responding directly).';
782
- const lines = specialists.map((name) => {
783
- const reason = reasons[name];
784
- return reason ? ` - ${name} (${reason})` : ` - ${name}`;
785
- });
786
- return `Engaging:\n${lines.join('\n')}`;
787
- }
788
-
789
- export function buildDispatchPlan({ track, intent, specialists = [] }) {
790
- if (track === EXECUTION_TRACKS.immediate) return 'Plan: respond directly.';
791
- if (track === EXECUTION_TRACKS.focused) return `Plan: ${specialists.join(' → ')}.`;
792
-
793
- const phases = ['cx-architect'];
794
- if (intent === INTENT_CLASSES.fix || intent === INTENT_CLASSES.investigation) phases.push('cx-debugger');
795
- phases.push('cx-engineer');
796
- const validators = specialists.filter((name) => ['cx-reviewer', 'cx-qa', 'cx-security'].includes(name));
797
- if (validators.length) phases.push(validators.join(' + '));
798
- return `Plan: ${phases.join(' → ')}.`;
799
- }
800
-
801
- // Format active flavor overlays into a one-line trace, e.g.
802
- // "Overlays: architect=platform (matched kubernetes, infra), engineer=ai (matched llm, agent)"
803
-
804
- export function formatOverlayTrace(roleFlavors = {}, request = '') {
805
- const text = String(request).toLowerCase();
806
- const active = Object.entries(roleFlavors).filter(([, flavor]) => flavor);
807
- if (active.length === 0) return '';
808
- const FLAVOR_KEYWORDS = {
809
- architect: ['agent', 'rag', 'retrieval', 'embedding', 'integration', 'webhook', 'warehouse', 'schema', 'sso', 'rbac', 'platform', 'kubernetes', 'infra'],
810
- productManager: ['platform', 'api', 'enterprise', 'ai product', 'agent', 'growth', 'activation'],
811
- qa: ['agent', 'prompt', 'api', 'sdk', 'pipeline', 'etl', 'ui', 'ux', 'accessibility'],
812
- security: ['prompt injection', 'privacy', 'pii', 'dependency', 'cloud', 'iam', 'auth', 'xss', 'csrf'],
813
- dataAnalyst: ['experiment', 'a/b', 'telemetry', 'metric', 'funnel'],
814
- dataEngineer: ['vector', 'embedding', 'warehouse', 'pipeline', 'etl', 'pgvector'],
815
- engineer: ['llm', 'agent', 'rag', 'kubernetes', 'k8s', 'terraform', 'infra', 'docker', 'pipeline', 'etl', 'data model'],
816
- };
817
- const parts = active.map(([role, flavor]) => {
818
- const kws = (FLAVOR_KEYWORDS[role] || []).filter((kw) => text.includes(kw));
819
- const matched = kws.slice(0, 3).join(', ');
820
- return matched ? `${role}=${flavor} (matched ${matched})` : `${role}=${flavor}`;
821
- });
822
- return `Overlays: ${parts.join(', ')}`;
823
- }
824
-
825
- function resolveArtifactReviewRequirements(docAuthoring) {
826
- if (!docAuthoring?.docType) {
827
- return { requiredReviewers: [], optionalReviewers: [], releaseGate: null };
828
- }
829
- const entry = getArtifactEntry(docAuthoring.docType);
830
- const gate = entry?.releaseGate ?? {};
831
- return {
832
- requiredReviewers: gate.requiredReviewers ?? [],
833
- optionalReviewers: gate.optionalReviewers ?? [],
834
- releaseGate: gate,
835
- };
836
- }
837
-
838
- // Map a free-text request to the decision names that gate it. Keyed against the
839
- // union of every team's forbiddenDecisions so a BLOCKED status fires only when
840
- // the request actually asks for a decision the primary team cannot make. Ordered
841
- // most-specific-first (deployment-timing before deployment) so the narrow match
842
- // wins.
843
-
844
- const REQUESTED_DECISION_PATTERNS = [
845
- [/\bsecurity[ -]?override\b|\boverride\s+(the\s+)?security\b/i, 'security-override'],
846
- [/\bdeployment[ -]?timing\b|\bwhen\s+to\s+deploy\b|\bdeploy\s+(?:immediately|now)\b/i, 'deployment-timing'],
847
- [/\bdeployment[ -]?readiness\b/i, 'deployment-readiness'],
848
- [/\bdeploy(?:ment|ing|s)?\b|\brelease\s+to\s+prod/i, 'deployment'],
849
- [/\binfra(?:structure)?[ -]?change\b|\bchange\s+(?:the\s+)?infra/i, 'infra-change'],
850
- [/\bscope[ -]?change\b|\bchange\s+(?:the\s+)?scope\b|\bre-?scope\b/i, 'scope-change'],
851
- [/\bproduct[ -]?scope\b/i, 'product-scope'],
852
- [/\buser[ -]?research\s+method/i, 'user-research-methods'],
853
- [/\buser[ -]?research\b/i, 'user-research'],
854
- [/\bimplementation[ -]?approach\b/i, 'implementation-approach'],
855
- [/\bimplementation[ -]?detail/i, 'implementation-details'],
856
- [/\bsecurity[ -]?policy\b/i, 'security-policy'],
857
- [/\bops[ -]?procedure|operational\s+procedure/i, 'ops-procedures'],
858
- [/\barchitect/i, 'architecture'],
859
- ];
860
-
861
- function detectRequestedDecisions(request = '') {
862
- const text = String(request || '');
863
- const found = new Set();
864
- for (const [pattern, decision] of REQUESTED_DECISION_PATTERNS) {
865
- if (pattern.test(text)) found.add(decision);
866
- }
867
- return Array.from(found);
868
- }
869
-
870
- /**
871
- * Build team-aware routing metadata (RFC-0004 §2).
872
- * Returns { primaryTeam, involvedTeams, requiredApprovals, escalationPath, blockedStatus }.
873
17
  *
874
- * The primary team is intent-driven (INTENT_TO_TEAM), falling back to the first
875
- * specialist's team when the intent has no mapping. involvedTeams unions the
876
- * primary team with every selected specialist's team. blockedStatus fires when
877
- * the primary team's forbiddenDecisions intersect the decisions the request asks
878
- * for; escalation walks the primary team's path first. Team membership and team
879
- * metadata resolve from specialists/org.
880
- */
881
- export function teamRoutingForSpecialists(specialists = [], { intent = null, request = '', cwd = null, docAuthoring = null } = {}) {
882
- let registry = null;
883
- try {
884
- registry = loadRegistry();
885
- } catch {
886
- return {
887
- primaryTeam: null,
888
- involvedTeams: [],
889
- requiredApprovals: [],
890
- escalationPath: [],
891
- blockedStatus: null,
892
- };
893
- }
894
-
895
- const profile = cwd ? resolveActiveScope(cwd) : null;
896
- const profileTeams = scopeTeamsById(profile);
897
-
898
- const teams = new Map();
899
- const involvedTeamIds = new Set();
900
- const addTeam = (teamId) => {
901
- if (!teamId) return;
902
- involvedTeamIds.add(teamId);
903
- if (teams.has(teamId)) return;
904
- const profileTeam = profileTeams ? resolveScopeTeamMeta(teamId, profile) : null;
905
- teams.set(teamId, profileTeam || registry.teams?.[teamId] || null);
906
- };
907
-
908
- for (const specialistId of specialists) {
909
- const cxId = specialistId.startsWith('cx-') ? specialistId : `cx-${specialistId}`;
910
- const specialist = registry.specialists?.[cxId];
911
- if (specialist?.team) addTeam(specialist.team);
912
- }
913
-
914
- // RFC-0004 §2 step 2: intent selects the primary owning team. It always counts
915
- // as involved even if no selected specialist sits on it yet.
916
- const profileIntentTeam = resolveIntentTeamForScope(intent, profile);
917
- const intentTeam = profileIntentTeam
918
- ?? (intent && INTENT_TO_TEAM[intent] ? INTENT_TO_TEAM[intent] : null);
919
- addTeam(intentTeam);
920
-
921
- const involvedTeams = Array.from(involvedTeamIds).sort();
922
- let primaryTeam = intentTeam || (involvedTeams.length > 0 ? involvedTeams[0] : null);
923
-
924
- if (docAuthoring?.owner) {
925
- const cxId = docAuthoring.owner.startsWith('cx-') ? docAuthoring.owner : `cx-${docAuthoring.owner}`;
926
- const ownerTeam = registry.specialists?.[cxId]?.team;
927
- if (ownerTeam) {
928
- addTeam(ownerTeam);
929
- primaryTeam = ownerTeam;
930
- }
931
- }
932
-
933
- // Collect required approvals from involved teams' decision rights.
934
- const requiredApprovals = new Set();
935
- for (const teamId of involvedTeamIds) {
936
- const team = teams.get(teamId);
937
- if (team?.decisionRights) {
938
- for (const decision of team.decisionRights) requiredApprovals.add(decision);
939
- }
940
- }
941
-
942
- // RFC-0004 §2 step 5: BLOCKED when the primary team forbids a decision the
943
- // request actually asks for. Escalate via that team's path.
944
- let blockedStatus = null;
945
- const primaryTeamObj = primaryTeam ? (teams.get(primaryTeam) || registry.teams?.[primaryTeam]) : null;
946
- if (Array.isArray(primaryTeamObj?.forbiddenDecisions)) {
947
- const blocked = detectRequestedDecisions(request)
948
- .filter((decision) => primaryTeamObj.forbiddenDecisions.includes(decision));
949
- if (blocked.length > 0) {
950
- blockedStatus = {
951
- team: primaryTeam,
952
- forbiddenDecisions: blocked,
953
- escalationPath: primaryTeamObj.escalationPath || [],
954
- };
955
- }
956
- }
957
-
958
- // Concatenate escalation paths, primary team first, de-duplicated.
959
- const escalationPath = [];
960
- const seen = new Set();
961
- const orderedTeams = primaryTeam
962
- ? [primaryTeam, ...involvedTeams.filter((t) => t !== primaryTeam)]
963
- : involvedTeams;
964
- for (const teamId of orderedTeams) {
965
- const team = teams.get(teamId) || registry.teams?.[teamId];
966
- if (Array.isArray(team?.escalationPath)) {
967
- for (const role of team.escalationPath) {
968
- if (!seen.has(role)) {
969
- escalationPath.push(role);
970
- seen.add(role);
971
- }
972
- }
973
- }
974
- }
975
-
976
- const squadId = primaryTeamObj?.kind === 'squad' ? primaryTeam : null;
977
- const groupId = primaryTeamObj?.kind === 'group' ? primaryTeam : (primaryTeamObj?.groupId ?? null);
978
-
979
- return {
980
- primaryTeam,
981
- squadId,
982
- groupId,
983
- collaborators: primaryTeamObj?.collaborators || [],
984
- involvedTeams,
985
- requiredApprovals: Array.from(requiredApprovals).sort(),
986
- escalationPath,
987
- blockedStatus,
988
- };
989
- }
990
-
991
- export function routeRequest(options = {}) {
992
- const intent = classifyIntent(options.request);
993
- const riskFlags = detectRiskFlags(options.request);
994
- const roleFlavors = classifyRoleFlavors(options.request);
995
- const productFlavor = isProductIntelligenceRequest(options.request)
996
- ? classifyProductManagerFlavor(options.request)
997
- : null;
998
- const workCategory = classifyWorkCategory(options.request, riskFlags);
999
- const track = determineExecutionTrack({ ...options, riskFlags });
1000
- const docAuthoring = detectDocAuthoringIntent(options.request);
1001
- const externalResearch = requiresExternalResearch({ request: options.request, workCategory, riskFlags });
1002
- const framingChallenge = requiresFramingChallenge({ request: options.request, workCategory, riskFlags, introducesContract: options.introducesContract });
1003
- const artifactReview = resolveArtifactReviewRequirements(docAuthoring);
1004
- const suggestedWorkflowType = suggestWorkflowType({ intent, request: options.request, docAuthoring });
1005
- const researchExecutionPolicy = intent === INTENT_CLASSES.research || externalResearch?.required
1006
- ? buildResearchExecutionPolicy({ request: options.request })
1007
- : null;
1008
- const signals = requestSignals(options.request, options.context);
1009
- const triggers = proactiveTriggers(signals);
1010
- const reasons = {};
1011
- for (const { specialist, reason } of triggers) {
1012
- reasons[specialist] = reason;
1013
- }
1014
-
1015
- const augmentOpts = {
1016
- request: options.request,
1017
- docAuthoring,
1018
- externalResearch,
1019
- framingChallenge,
1020
- artifactReview,
1021
- triggers,
1022
- };
1023
-
1024
- let specialists = augmentSpecialists(
1025
- selectSpecialists({ ...options, intent, track, riskFlags, workCategory }),
1026
- augmentOpts,
1027
- );
1028
-
1029
- const policyTrack = track === EXECUTION_TRACKS.immediate ? EXECUTION_TRACKS.focused : track;
1030
- let policySpecialists = augmentSpecialists(
1031
- selectSpecialists({ ...options, intent, track: policyTrack, riskFlags, workCategory }),
1032
- augmentOpts,
1033
- );
1034
- policySpecialists = enrichPolicyForProjectQuestion(options.request, policySpecialists);
1035
-
1036
- const displaySpecialists = specialists.length ? specialists : policySpecialists;
1037
-
1038
- const contractChain = resolveContractChain({
1039
- intent,
1040
- workCategory,
1041
- track,
1042
- riskFlags,
1043
- specialists: displaySpecialists,
1044
- framingChallenge,
1045
- externalResearch,
1046
- docAuthoring,
1047
- artifactReview,
1048
- });
1049
-
1050
- // Team-aware routing: analyze specialist-to-team mapping and identify
1051
- // team involvement, primary owner, required approvals, and escalation paths.
1052
- const teamRouting = teamRoutingForSpecialists(displaySpecialists, {
1053
- intent,
1054
- request: options.request,
1055
- cwd: options.cwd ?? null,
1056
- docAuthoring,
1057
- });
1058
-
1059
- const route = {
1060
- intent,
1061
- workCategory,
1062
- track,
1063
- riskFlags,
1064
- productFlavor,
1065
- roleFlavors,
1066
- specialists,
1067
- policySpecialists,
1068
- displaySpecialists,
1069
- signals,
1070
- triggers,
1071
- dispatchReasons: reasons,
1072
- docAuthoring,
1073
- externalResearch,
1074
- framingChallenge,
1075
- artifactReview,
1076
- suggestedWorkflowType,
1077
- researchExecutionPolicy,
1078
- contractChain,
1079
- teamRouting,
1080
- dispatchPlan: buildDispatchPlan({ track, intent, specialists: displaySpecialists }),
1081
- dispatchSummary: buildDispatchSummary({ specialists: displaySpecialists, reasons }),
1082
- };
1083
-
1084
- if (process.env.CONSTRUCT_VERBOSE === '1' && triggers.length > 0) {
1085
- for (const { specialist, reason } of triggers) {
1086
- console.error(`[route] proactive: ${specialist} (${reason})`);
1087
- }
1088
- }
1089
-
1090
- return route;
1091
- }
1092
-
1093
- // Sync wrapper that returns the keyword route immediately and fires the
1094
- // LLM intent verifier in the background. The verifier writes its verdict
1095
- // to ~/.cx/intent-verifications.jsonl for offline tuning; the dispatched
1096
- // route never waits on a model round-trip. Disable the background call
1097
- // entirely with CONSTRUCT_INTENT_VERIFY=off — see lib/intent-classifier.mjs.
1098
- export function routeRequestVerified(options = {}) {
1099
- const route = routeRequest(options);
1100
- return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
1101
- }
1102
-
1103
- /**
1104
- * Build the construct→orchestrator contract packet (specialists/org).
1105
- * Callers must pass this object to agent_contract — not a bare goal string.
18
+ * Thin re-export layer (construct-rf26.10): the actual
19
+ * implementation is split by concern into lib/orchestration/:
20
+ * - policy-constants.mjs — EXECUTION_TRACKS/INTENT_CLASSES/etc enums
21
+ * - classification.mjs — intent/work-category/risk-flag/flavor classifiers
22
+ * - gates.mjs — research/framing/approval gates, team routing
23
+ * - flow-selection.mjs — specialist selection + routeRequest and friends
24
+ * Kept as a single re-export surface (rather than updating every import site
25
+ * to the new paths) because lib/orchestration-policy.mjs is imported by ~25
26
+ * other modules and test files; splitting the export surface without
27
+ * splitting every caller would multiply the blast radius of this refactor
28
+ * for no behavioral benefit. See construct-rf26.10 for the tradeoff.
1106
29
  */
1107
- export function buildConstructToOrchestratorPacket(options = {}) {
1108
- const request = String(options.request || '');
1109
- const route = options.route || routeRequest({
1110
- request,
1111
- fileCount: options.fileCount ?? 0,
1112
- moduleCount: options.moduleCount ?? 0,
1113
- introducesContract: options.introducesContract ?? false,
1114
- explicitDrive: options.explicitDrive ?? false,
1115
- });
1116
- if (route.track === EXECUTION_TRACKS.immediate) return null;
30
+ export {
31
+ EXECUTION_TRACKS,
32
+ INTENT_CLASSES,
33
+ INTENT_TO_TEAM,
34
+ WORK_CATEGORIES,
35
+ TERMINAL_STATES,
36
+ } from './orchestration/policy-constants.mjs';
1117
37
 
1118
- const goal = String(options.goal || request || '').trim() || 'User-requested work';
1119
- const acceptanceCriteria = Array.isArray(options.acceptanceCriteria) && options.acceptanceCriteria.length
1120
- ? options.acceptanceCriteria
1121
- : ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
38
+ export { ownerForEvent, ownerForDoc } from './orchestration/routing-tables.mjs';
1122
39
 
1123
- return {
1124
- goal,
1125
- intent: route.intent,
1126
- workCategory: route.workCategory,
1127
- riskFlags: route.riskFlags || {},
1128
- suggestedWorkflowType: route.suggestedWorkflowType || null,
1129
- acceptanceCriteria,
1130
- };
1131
- }
40
+ export {
41
+ resolveDocTypeMention,
42
+ detectDocAuthoringIntent,
43
+ extractNamedEntities,
44
+ classifyResearchShape,
45
+ requiresLiveWebAccess,
46
+ isProductIntelligenceRequest,
47
+ classifyProductManagerFlavor,
48
+ classifyArchitectFlavor,
49
+ classifyQaFlavor,
50
+ classifySecurityFlavor,
51
+ classifyDataAnalystFlavor,
52
+ classifyDataEngineerFlavor,
53
+ classifyEngineerFlavor,
54
+ isDataAnalysisRequest,
55
+ isDataEngineeringRequest,
56
+ isLegalComplianceRequest,
57
+ isBusinessStrategyRequest,
58
+ isOperationsPlanningRequest,
59
+ isRdLeadRequest,
60
+ isExplorerRequest,
61
+ isVisualDeliverableRequest,
62
+ classifyRoleFlavors,
63
+ formatOverlaySelection,
64
+ detectRiskFlags,
65
+ classifyIntent,
66
+ classifyWorkCategory,
67
+ determineExecutionTrack,
68
+ } from './orchestration/classification.mjs';
69
+
70
+ export {
71
+ requiresExternalResearch,
72
+ requiresFramingChallenge,
73
+ requiresExecutiveApproval,
74
+ teamRoutingForSpecialists,
75
+ } from './orchestration/gates.mjs';
76
+
77
+ export {
78
+ selectSpecialists,
79
+ augmentSpecialists,
80
+ identifyParallelChecks,
81
+ requestSignals,
82
+ proactiveTriggers,
83
+ formatOverlayTrace,
84
+ routeRequest,
85
+ routeRequestVerified,
86
+ buildConstructToOrchestratorPacket,
87
+ } from './orchestration/flow-selection.mjs';