@geraldmaron/construct 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/.env.example +10 -7
  2. package/LICENSE +201 -21
  3. package/README.md +132 -257
  4. package/agents/contracts.json +387 -0
  5. package/agents/prompts/cx-accessibility.md +8 -0
  6. package/agents/prompts/cx-ai-engineer.md +92 -0
  7. package/agents/prompts/cx-architect.md +10 -2
  8. package/agents/prompts/cx-business-strategist.md +13 -0
  9. package/agents/prompts/cx-data-analyst.md +80 -0
  10. package/agents/prompts/cx-data-engineer.md +4 -0
  11. package/agents/prompts/cx-debugger.md +8 -0
  12. package/agents/prompts/cx-designer.md +19 -0
  13. package/agents/prompts/cx-devil-advocate.md +4 -0
  14. package/agents/prompts/cx-docs-keeper.md +128 -0
  15. package/agents/prompts/cx-engineer.md +13 -0
  16. package/agents/prompts/cx-evaluator.md +4 -0
  17. package/agents/prompts/cx-explorer.md +12 -0
  18. package/agents/prompts/cx-legal-compliance.md +13 -0
  19. package/agents/prompts/cx-operations.md +13 -0
  20. package/agents/prompts/cx-orchestrator.md +107 -4
  21. package/agents/prompts/cx-platform-engineer.md +75 -0
  22. package/agents/prompts/cx-product-manager.md +8 -0
  23. package/agents/prompts/cx-qa.md +100 -0
  24. package/agents/prompts/cx-rd-lead.md +13 -0
  25. package/agents/prompts/cx-release-manager.md +8 -0
  26. package/agents/prompts/cx-researcher.md +21 -1
  27. package/agents/prompts/cx-reviewer.md +8 -0
  28. package/agents/prompts/cx-security.md +104 -0
  29. package/agents/prompts/cx-sre.md +104 -0
  30. package/agents/prompts/cx-test-automation.md +4 -0
  31. package/agents/prompts/cx-trace-reviewer.md +7 -3
  32. package/agents/prompts/cx-ux-researcher.md +4 -0
  33. package/agents/registry.json +365 -90
  34. package/agents/role-manifests.json +217 -0
  35. package/bin/construct +3345 -141
  36. package/bin/construct-postinstall.mjs +87 -0
  37. package/commands/build/feature.md +6 -6
  38. package/commands/plan/feature.md +6 -5
  39. package/commands/plan/requirements.md +1 -1
  40. package/commands/ship/status.md +1 -1
  41. package/commands/work/drive.md +3 -3
  42. package/commands/work/optimize-prompts.md +3 -3
  43. package/db/{migrations → schema}/001_init.sql +2 -0
  44. package/db/schema/002_pgvector.sql +182 -0
  45. package/db/schema/003_intake.sql +47 -0
  46. package/examples/README.md +85 -0
  47. package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
  48. package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
  49. package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
  50. package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
  51. package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
  52. package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
  53. package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
  54. package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
  55. package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
  56. package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
  57. package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
  58. package/examples/personas/construct/bad/commit-without-approval.md +30 -0
  59. package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
  60. package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
  61. package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
  62. package/examples/provider-plugin/README.md +34 -0
  63. package/examples/provider-plugin/index.mjs +74 -0
  64. package/examples/provider-plugin/package.json +15 -0
  65. package/examples/seed-observations/README.md +38 -0
  66. package/examples/seed-observations/anti-patterns.md +44 -0
  67. package/examples/seed-observations/decisions.md +36 -0
  68. package/examples/seed-observations/patterns.md +42 -0
  69. package/lib/agent-contracts-enforce.mjs +158 -0
  70. package/lib/agent-contracts.mjs +231 -0
  71. package/lib/agents/postconditions.mjs +126 -0
  72. package/lib/agents/schema.mjs +124 -0
  73. package/lib/artifact-capture.mjs +183 -0
  74. package/lib/audit-trail.mjs +149 -0
  75. package/lib/auto-docs.mjs +245 -65
  76. package/lib/beads/auto-close.mjs +126 -0
  77. package/lib/beads/drift.mjs +171 -0
  78. package/lib/beads-automation.mjs +542 -0
  79. package/lib/beads-client.mjs +518 -0
  80. package/lib/beads-lock.mjs +377 -0
  81. package/lib/beads-optimistic.mjs +365 -0
  82. package/lib/bootstrap/built-ins.mjs +136 -0
  83. package/lib/bootstrap/lazy-install.mjs +161 -0
  84. package/lib/bootstrap/resources.mjs +120 -0
  85. package/lib/bootstrap.mjs +105 -0
  86. package/lib/cache-governor.js +213 -0
  87. package/lib/cache-strategy-anthropic.js +62 -0
  88. package/lib/cache-strategy-google.js +79 -0
  89. package/lib/cache-strategy-none.js +30 -0
  90. package/lib/cache-strategy-openai.js +48 -0
  91. package/lib/cache-strategy.js +91 -0
  92. package/lib/claude-allow.mjs +149 -0
  93. package/lib/cli-commands.mjs +413 -258
  94. package/lib/codex-config.mjs +3 -1
  95. package/lib/comment-lint.mjs +55 -6
  96. package/lib/completions.mjs +21 -6
  97. package/lib/config/alias.mjs +56 -0
  98. package/lib/config/project-config.mjs +335 -0
  99. package/lib/config/schema.mjs +159 -0
  100. package/lib/context-router.mjs +308 -0
  101. package/lib/cost-ledger.mjs +177 -0
  102. package/lib/cost.mjs +171 -10
  103. package/lib/dashboard-static.mjs +158 -0
  104. package/lib/deployment-mode.mjs +86 -0
  105. package/lib/deprecate.mjs +49 -0
  106. package/lib/dispatch-batch.js +183 -0
  107. package/lib/distill.mjs +21 -8
  108. package/lib/doc-stamp.mjs +164 -0
  109. package/lib/doc-verify.mjs +119 -0
  110. package/lib/docs-routing.mjs +89 -0
  111. package/lib/docs-verify.mjs +417 -0
  112. package/lib/doctor/audit.mjs +71 -0
  113. package/lib/doctor/cli.mjs +99 -0
  114. package/lib/doctor/escalate.mjs +29 -0
  115. package/lib/doctor/index.mjs +140 -0
  116. package/lib/doctor/report.mjs +170 -0
  117. package/lib/doctor/watchers/bd-watch.mjs +117 -0
  118. package/lib/doctor/watchers/cost.mjs +130 -0
  119. package/lib/doctor/watchers/disk.mjs +122 -0
  120. package/lib/doctor/watchers/handoffs.mjs +33 -0
  121. package/lib/doctor/watchers/process-pressure.mjs +60 -0
  122. package/lib/doctor/watchers/service-health.mjs +188 -0
  123. package/lib/document-extract.mjs +288 -0
  124. package/lib/document-ingest.mjs +230 -0
  125. package/lib/drop.mjs +282 -0
  126. package/lib/embed/approval-queue.mjs +176 -0
  127. package/lib/embed/artifact.mjs +349 -0
  128. package/lib/embed/authority-guard.mjs +155 -0
  129. package/lib/embed/cli.mjs +408 -0
  130. package/lib/embed/config.mjs +355 -0
  131. package/lib/embed/conflict-detection.mjs +264 -0
  132. package/lib/embed/customer-profiles.mjs +480 -0
  133. package/lib/embed/daemon.mjs +1309 -0
  134. package/lib/embed/demand-fetch.mjs +449 -0
  135. package/lib/embed/docs-lifecycle.mjs +349 -0
  136. package/lib/embed/inbox-live-watcher.mjs +119 -0
  137. package/lib/embed/inbox.mjs +343 -0
  138. package/lib/embed/intake-metrics.mjs +190 -0
  139. package/lib/embed/jobs/vector-sync.mjs +198 -0
  140. package/lib/embed/notifications.mjs +75 -0
  141. package/lib/embed/output.mjs +79 -0
  142. package/lib/embed/providers/github.mjs +295 -0
  143. package/lib/embed/providers/jira.mjs +192 -0
  144. package/lib/embed/providers/linear.mjs +186 -0
  145. package/lib/embed/providers/registry.mjs +115 -0
  146. package/lib/embed/providers/slack.mjs +203 -0
  147. package/lib/embed/recommendation-store.mjs +378 -0
  148. package/lib/embed/roadmap.mjs +374 -0
  149. package/lib/embed/role-framing.mjs +110 -0
  150. package/lib/embed/scheduler.mjs +99 -0
  151. package/lib/embed/semantic.mjs +325 -0
  152. package/lib/embed/snapshot.mjs +191 -0
  153. package/lib/embed/supervision.mjs +235 -0
  154. package/lib/embed/target-resolver.mjs +186 -0
  155. package/lib/embed/worker.mjs +62 -0
  156. package/lib/embed/workspaces.mjs +297 -0
  157. package/lib/engine/chunker-headings.mjs +110 -0
  158. package/lib/engine/compressor-heuristic.mjs +100 -0
  159. package/lib/engine/consolidate.mjs +287 -0
  160. package/lib/engine/contracts.mjs +126 -0
  161. package/lib/engine/defaults.mjs +129 -0
  162. package/lib/engine/eval-retrieval.mjs +148 -0
  163. package/lib/engine/fuser-rrf.mjs +62 -0
  164. package/lib/engine/index.mjs +37 -0
  165. package/lib/engine/registry.mjs +146 -0
  166. package/lib/engine/reranker-mmr.mjs +90 -0
  167. package/lib/engine/tokens.mjs +77 -0
  168. package/lib/entity-store.mjs +280 -0
  169. package/lib/env-config.mjs +69 -4
  170. package/lib/evals/retrieval-bench.mjs +159 -0
  171. package/lib/evaluator-optimizer.mjs +317 -0
  172. package/lib/features.mjs +159 -29
  173. package/lib/gates-audit.mjs +236 -0
  174. package/lib/git-hooks/prepare-commit-msg +58 -0
  175. package/lib/handoffs/cleanup.mjs +159 -0
  176. package/lib/handoffs/contract.mjs +162 -0
  177. package/lib/handoffs/inventory.mjs +120 -0
  178. package/lib/headhunt.mjs +28 -47
  179. package/lib/health-check.mjs +399 -0
  180. package/lib/hook-health.mjs +442 -0
  181. package/lib/hooks/_lib/log.mjs +82 -0
  182. package/lib/hooks/adaptive-lint.mjs +26 -2
  183. package/lib/hooks/agent-tracker.mjs +171 -14
  184. package/lib/hooks/audit-reads.mjs +109 -0
  185. package/lib/hooks/audit-trail.mjs +153 -0
  186. package/lib/hooks/bash-output-logger.mjs +71 -0
  187. package/lib/hooks/block-no-verify.mjs +41 -0
  188. package/lib/hooks/ci-status-check.mjs +82 -0
  189. package/lib/hooks/comment-lint.mjs +29 -7
  190. package/lib/hooks/config-protection.mjs +39 -18
  191. package/lib/hooks/context-watch.mjs +137 -0
  192. package/lib/hooks/context-window-recovery.mjs +4 -20
  193. package/lib/hooks/dep-audit.mjs +16 -0
  194. package/lib/hooks/doc-coupling-check.mjs +80 -0
  195. package/lib/hooks/edit-accumulator.mjs +3 -0
  196. package/lib/hooks/edit-error-recovery.mjs +3 -0
  197. package/lib/hooks/edit-guard.mjs +45 -4
  198. package/lib/hooks/env-check.mjs +3 -0
  199. package/lib/hooks/guard-bash.mjs +58 -1
  200. package/lib/hooks/mcp-audit.mjs +18 -20
  201. package/lib/hooks/mcp-health-check.mjs +36 -0
  202. package/lib/hooks/model-fallback.mjs +42 -56
  203. package/lib/hooks/policy-engine.mjs +209 -0
  204. package/lib/hooks/post-merge-docs-check.mjs +63 -0
  205. package/lib/hooks/pre-compact.mjs +4 -24
  206. package/lib/hooks/pre-push-gate.mjs +200 -37
  207. package/lib/hooks/proactive-activation.mjs +284 -0
  208. package/lib/hooks/read-tracker.mjs +27 -4
  209. package/lib/hooks/readme-age-check.mjs +77 -0
  210. package/lib/hooks/registry-sync.mjs +12 -5
  211. package/lib/hooks/scan-secrets.mjs +50 -7
  212. package/lib/hooks/session-optimize.mjs +311 -0
  213. package/lib/hooks/session-start.mjs +287 -28
  214. package/lib/hooks/stop-notify.mjs +236 -96
  215. package/lib/hooks/stop-typecheck.mjs +13 -1
  216. package/lib/hooks/test-watch.mjs +68 -0
  217. package/lib/host-capabilities.mjs +31 -10
  218. package/lib/init-docs.mjs +731 -291
  219. package/lib/init-unified.mjs +1116 -0
  220. package/lib/init-update.mjs +168 -0
  221. package/lib/init.mjs +107 -0
  222. package/lib/install/first-invocation.mjs +119 -0
  223. package/lib/install/stage-project.mjs +69 -0
  224. package/lib/intake/classify.mjs +278 -0
  225. package/lib/intake/feedback.mjs +273 -0
  226. package/lib/intake/filesystem-queue.mjs +158 -0
  227. package/lib/intake/intake-config.mjs +132 -0
  228. package/lib/intake/postgres-queue.mjs +198 -0
  229. package/lib/intake/prepare.mjs +139 -0
  230. package/lib/intake/queue.mjs +83 -0
  231. package/lib/intake/session-prelude.mjs +50 -0
  232. package/lib/integrations/intake-integrations.mjs +740 -0
  233. package/lib/intent-classifier.mjs +253 -0
  234. package/lib/knowledge/layout.mjs +72 -0
  235. package/lib/knowledge/rag.mjs +331 -0
  236. package/lib/knowledge/search.mjs +315 -0
  237. package/lib/knowledge/trends.mjs +261 -0
  238. package/lib/logger.mjs +85 -0
  239. package/lib/mcp/broker.mjs +124 -0
  240. package/lib/mcp/server.mjs +554 -854
  241. package/lib/mcp/tools/document.mjs +132 -0
  242. package/lib/mcp/tools/memory.mjs +209 -0
  243. package/lib/mcp/tools/project.mjs +349 -0
  244. package/lib/mcp/tools/skills.mjs +409 -0
  245. package/lib/mcp/tools/storage.mjs +60 -0
  246. package/lib/mcp/tools/telemetry.mjs +315 -0
  247. package/lib/mcp/tools/workflow.mjs +112 -0
  248. package/lib/mcp-catalog.json +54 -3
  249. package/lib/mcp-manager.mjs +96 -48
  250. package/lib/mcp-platform-config.mjs +22 -22
  251. package/lib/memory-stats.mjs +121 -0
  252. package/lib/mode-commands.mjs +124 -0
  253. package/lib/model-free-selector.mjs +184 -0
  254. package/lib/model-pricing.mjs +152 -0
  255. package/lib/model-registry.mjs +226 -0
  256. package/lib/model-router.mjs +362 -386
  257. package/lib/observation-store.mjs +391 -0
  258. package/lib/ollama-manager.mjs +428 -0
  259. package/lib/opencode-config.mjs +13 -3
  260. package/lib/opencode-runtime-plugin.mjs +70 -38
  261. package/lib/opencode-telemetry.mjs +64 -6
  262. package/lib/orchestration-policy.mjs +509 -6
  263. package/lib/overrides/resolver.mjs +207 -0
  264. package/lib/parity.mjs +147 -0
  265. package/lib/paths.mjs +33 -0
  266. package/lib/performance/generate.mjs +212 -0
  267. package/lib/plugin-registry.mjs +268 -0
  268. package/lib/policy/engine.mjs +130 -0
  269. package/lib/policy/unified-gates.mjs +96 -0
  270. package/lib/project-detection.mjs +129 -0
  271. package/lib/project-init-shared.mjs +272 -0
  272. package/lib/project-profile.mjs +447 -0
  273. package/lib/prompt-composer.js +434 -0
  274. package/lib/prompt-metadata.mjs +1 -1
  275. package/lib/provider-capabilities-anthropic.js +44 -0
  276. package/lib/provider-capabilities-deepseek.js +37 -0
  277. package/lib/provider-capabilities-generic.js +26 -0
  278. package/lib/provider-capabilities-google.js +47 -0
  279. package/lib/provider-capabilities-openai.js +45 -0
  280. package/lib/provider-capabilities.js +142 -0
  281. package/lib/providers/atlassian-confluence/index.mjs +103 -0
  282. package/lib/providers/atlassian-jira/index.mjs +100 -0
  283. package/lib/providers/auth-manager.mjs +126 -0
  284. package/lib/providers/circuit-breaker.mjs +124 -0
  285. package/lib/providers/contract.mjs +93 -0
  286. package/lib/providers/github/index.mjs +126 -0
  287. package/lib/providers/registry.mjs +184 -0
  288. package/lib/providers/salesforce/index.mjs +100 -0
  289. package/lib/providers/slack/index.mjs +80 -0
  290. package/lib/reflect.mjs +137 -0
  291. package/lib/research-lint.mjs +164 -0
  292. package/lib/resources/budget.mjs +259 -0
  293. package/lib/role-preload.mjs +21 -6
  294. package/lib/roles/approval-surface.mjs +54 -0
  295. package/lib/roles/cli.mjs +118 -0
  296. package/lib/roles/event-bus.mjs +79 -0
  297. package/lib/roles/fence.mjs +84 -0
  298. package/lib/roles/gateway.mjs +260 -0
  299. package/lib/roles/hook-emit.mjs +37 -0
  300. package/lib/roles/manifest.mjs +48 -0
  301. package/lib/roles/router.mjs +27 -0
  302. package/lib/runtime-pressure.mjs +360 -0
  303. package/lib/schema-artifact.mjs +134 -0
  304. package/lib/schema-infer.mjs +551 -0
  305. package/lib/server/auth.mjs +168 -0
  306. package/lib/server/chat.mjs +336 -0
  307. package/lib/server/cors.mjs +77 -0
  308. package/lib/server/csrf.mjs +91 -0
  309. package/lib/server/index.mjs +1927 -78
  310. package/lib/server/insights.mjs +765 -0
  311. package/lib/server/rate-limit.mjs +91 -0
  312. package/lib/server/static/assets/index-ab25c707.js +70 -0
  313. package/lib/server/static/assets/index-f0c80a2b.css +1 -0
  314. package/lib/server/static/index.html +12 -817
  315. package/lib/server/telemetry-login.mjs +108 -0
  316. package/lib/server/webhook.mjs +510 -0
  317. package/lib/service-manager.mjs +522 -58
  318. package/lib/services/pattern-promotion-service.mjs +167 -0
  319. package/lib/services/telemetry-backend.mjs +178 -0
  320. package/lib/session-store.mjs +374 -0
  321. package/lib/setup-prompts.mjs +96 -0
  322. package/lib/setup.mjs +525 -38
  323. package/lib/skills-apply.mjs +280 -0
  324. package/lib/skills-scope.mjs +118 -0
  325. package/lib/status.mjs +261 -70
  326. package/lib/storage/admin.mjs +355 -0
  327. package/lib/storage/backend.mjs +2 -1
  328. package/lib/storage/backup.mjs +347 -0
  329. package/lib/storage/embeddings-engine.mjs +133 -0
  330. package/lib/storage/embeddings-legacy.mjs +85 -0
  331. package/lib/storage/embeddings-local.mjs +108 -0
  332. package/lib/storage/embeddings-ollama.mjs +78 -0
  333. package/lib/storage/embeddings-openai.mjs +85 -0
  334. package/lib/storage/embeddings.mjs +92 -33
  335. package/lib/storage/file-lock.mjs +130 -0
  336. package/lib/storage/fusion.mjs +95 -0
  337. package/lib/storage/hybrid-query.mjs +34 -27
  338. package/lib/storage/migrations.mjs +187 -0
  339. package/lib/storage/postgres-backup.mjs +124 -0
  340. package/lib/storage/sql-store.mjs +5 -15
  341. package/lib/storage/state-source.mjs +12 -13
  342. package/lib/storage/store-version.mjs +115 -0
  343. package/lib/storage/sync.mjs +144 -35
  344. package/lib/storage/unified-storage.mjs +550 -0
  345. package/lib/storage/vector-client.mjs +286 -0
  346. package/lib/storage/vector-store.mjs +71 -30
  347. package/lib/task-graph/generate.mjs +135 -0
  348. package/lib/task-graph/schema.mjs +81 -0
  349. package/lib/task-graph/store.mjs +71 -0
  350. package/lib/telemetry/backends/local.mjs +62 -0
  351. package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
  352. package/lib/telemetry/backfill.mjs +180 -0
  353. package/lib/telemetry/eval-datasets.mjs +203 -0
  354. package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
  355. package/lib/telemetry/intent-verifications.mjs +86 -0
  356. package/lib/telemetry/llm-judge.mjs +350 -0
  357. package/lib/telemetry/model-pricing-catalog.mjs +557 -0
  358. package/lib/telemetry/setup.mjs +151 -0
  359. package/lib/telemetry/skill-calls.mjs +78 -0
  360. package/lib/telemetry/team-rollup.mjs +4 -4
  361. package/lib/token-engine.js +117 -0
  362. package/lib/token-estimator-anthropic.js +15 -0
  363. package/lib/token-estimator-deepseek.js +13 -0
  364. package/lib/token-estimator-default.js +13 -0
  365. package/lib/token-estimator-google.js +13 -0
  366. package/lib/token-estimator-openai.js +13 -0
  367. package/lib/toolkit-env.mjs +1 -1
  368. package/lib/tty-prompts.mjs +211 -0
  369. package/lib/uninstall/uninstall.mjs +423 -0
  370. package/lib/update.mjs +115 -0
  371. package/lib/upgrade.mjs +141 -0
  372. package/lib/validator.mjs +51 -2
  373. package/lib/validators/skills.mjs +142 -0
  374. package/lib/wireframe.mjs +422 -0
  375. package/lib/worker/entrypoint.mjs +241 -0
  376. package/lib/worker/evidence.mjs +107 -0
  377. package/lib/worker/run.mjs +154 -0
  378. package/lib/worker/trace.mjs +182 -0
  379. package/lib/workflow-state.mjs +14 -18
  380. package/package.json +21 -8
  381. package/personas/construct.md +53 -51
  382. package/platforms/claude/CLAUDE.md +1 -1
  383. package/platforms/claude/settings.template.json +115 -68
  384. package/platforms/opencode/config.template.json +3 -7
  385. package/rules/common/agents.md +11 -38
  386. package/rules/common/beads-hygiene.md +75 -0
  387. package/rules/common/code-review.md +11 -100
  388. package/rules/common/coding-style.md +5 -3
  389. package/rules/common/comments.md +30 -111
  390. package/rules/common/commit-approval.md +53 -0
  391. package/rules/common/cx-agent-routing.md +1 -0
  392. package/rules/common/development-workflow.md +23 -41
  393. package/rules/common/doc-ownership.md +54 -0
  394. package/rules/common/efficiency.md +57 -0
  395. package/rules/common/framing.md +76 -0
  396. package/rules/common/git-workflow.md +2 -4
  397. package/rules/common/patterns.md +3 -7
  398. package/rules/common/performance.md +15 -31
  399. package/rules/common/release-gates.md +69 -0
  400. package/rules/common/research.md +107 -0
  401. package/rules/common/security.md +6 -6
  402. package/rules/common/skill-composition.md +67 -0
  403. package/rules/common/testing.md +15 -27
  404. package/rules/golang/hooks.md +0 -4
  405. package/rules/policy/bootstrap.yaml +11 -0
  406. package/rules/policy/drive.yaml +8 -0
  407. package/rules/policy/task.yaml +9 -0
  408. package/rules/policy/workflow.yaml +9 -0
  409. package/rules/python/hooks.md +0 -4
  410. package/rules/swift/hooks.md +0 -4
  411. package/rules/typescript/hooks.md +0 -4
  412. package/rules/web/hooks.md +0 -2
  413. package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
  414. package/skills/ai/prompt-optimizer.md +7 -7
  415. package/skills/compliance/ai-disclosure.md +58 -0
  416. package/skills/compliance/data-privacy.md +46 -0
  417. package/skills/compliance/license-audit.md +40 -0
  418. package/skills/compliance/regulatory-review.md +61 -0
  419. package/skills/docs/document-ingest-workflow.md +52 -0
  420. package/skills/docs/evidence-ingest-workflow.md +9 -0
  421. package/skills/docs/init-docs.md +51 -18
  422. package/skills/docs/product-intelligence-workflow.md +12 -0
  423. package/skills/docs/product-signal-workflow.md +4 -0
  424. package/skills/docs/research-workflow.md +23 -8
  425. package/skills/docs/runbook-workflow.md +1 -1
  426. package/skills/operating/orchestration-reference.md +151 -0
  427. package/skills/roles/architect.md +5 -0
  428. package/skills/roles/engineer.md +32 -0
  429. package/skills/roles/operator.md +12 -0
  430. package/skills/roles/reviewer.md +23 -0
  431. package/skills/routing.md +1 -1
  432. package/templates/devcontainer/Dockerfile.devcontainer +38 -0
  433. package/templates/devcontainer/devcontainer.json +31 -0
  434. package/templates/distribution/bootstrap.ps1 +142 -0
  435. package/templates/distribution/bootstrap.sh +196 -0
  436. package/templates/distribution/run.mjs +187 -0
  437. package/templates/docs/adr.md +44 -8
  438. package/templates/docs/changelog-entry.md +43 -0
  439. package/templates/docs/construct_guide.md +149 -0
  440. package/templates/docs/evidence-brief.md +2 -2
  441. package/templates/docs/meta-prd.md +140 -19
  442. package/templates/docs/onboarding.md +57 -0
  443. package/templates/docs/prd.md +159 -15
  444. package/templates/docs/research-brief.md +4 -4
  445. package/templates/homebrew/construct.rb +67 -0
  446. package/langfuse/docker-compose.yml +0 -82
  447. package/lib/eval-harness.mjs +0 -59
  448. package/lib/hooks/bootstrap-guard.mjs +0 -90
  449. package/lib/hooks/console-warn.mjs +0 -43
  450. package/lib/hooks/continuation-enforcer.mjs +0 -72
  451. package/lib/hooks/drive-guard.mjs +0 -89
  452. package/lib/hooks/mcp-task-scope.mjs +0 -47
  453. package/lib/hooks/task-completed-guard.mjs +0 -43
  454. package/lib/hooks/teammate-idle-guard.mjs +0 -54
  455. package/lib/hooks/workflow-guard.mjs +0 -62
  456. package/lib/prompt-composer.mjs +0 -196
  457. package/lib/review.mjs +0 -429
  458. package/lib/server/static/app.js +0 -841
  459. package/lib/telemetry/langfuse-model-sync.mjs +0 -270
  460. package/rules/common/hooks.md +0 -35
  461. package/rules/zh/README.md +0 -113
  462. package/rules/zh/agents.md +0 -55
  463. package/rules/zh/code-review.md +0 -129
  464. package/rules/zh/coding-style.md +0 -53
  465. package/rules/zh/development-workflow.md +0 -49
  466. package/rules/zh/git-workflow.md +0 -29
  467. package/rules/zh/hooks.md +0 -35
  468. package/rules/zh/patterns.md +0 -36
  469. package/rules/zh/performance.md +0 -60
  470. package/rules/zh/security.md +0 -34
  471. package/rules/zh/testing.md +0 -34
@@ -1,6 +1,20 @@
1
1
  /**
2
2
  * lib/orchestration-policy.mjs — provider-agnostic routing and escalation policy.
3
+ *
4
+ * Routing surfaces three things every call:
5
+ * 1. execution track + specialist list (who runs, in what order)
6
+ * 2. framing/research/doc-ownership gates (what must be true before work starts)
7
+ * 3. contract chain (resolveContractChain) (what the typed handoffs are)
8
+ *
9
+ * Agent-to-agent contracts are defined in agents/contracts.json and loaded via
10
+ * lib/agent-contracts.mjs. That file is the single source of truth for
11
+ * producer→consumer expectations, replacing the scattered DOC_OWNERSHIP map,
12
+ * SPECIALIST_MAP, and informal "collaborators" lists for anything contract-
13
+ * shaped. The older maps remain for quick lookups but defer to contracts.json
14
+ * for authoritative semantics.
3
15
  */
16
+ import { resolveContractChain } from './agent-contracts.mjs';
17
+ import { verifyRoute } from './intent-classifier.mjs';
4
18
 
5
19
  export const EXECUTION_TRACKS = {
6
20
  immediate: 'immediate',
@@ -26,6 +40,199 @@ export const WORK_CATEGORIES = {
26
40
 
27
41
  export const TERMINAL_STATES = ['DONE', 'BLOCKED', 'NEEDS_MAIN_INPUT'];
28
42
 
43
+ /**
44
+ * Maps document types to the specialist that owns authoring them.
45
+ * Construct (or any general persona) must route to the owner rather than
46
+ * authoring these directly — authoring is how the owner's domain checks
47
+ * (framing, research, trade-off analysis) actually fire.
48
+ *
49
+ * Keep in sync with rules/common/doc-ownership.md.
50
+ */
51
+ export const DOC_OWNERSHIP = {
52
+ prd: 'cx-product-manager',
53
+ 'meta-prd': 'cx-product-manager',
54
+ 'prd-platform': 'cx-product-manager',
55
+ 'prd-business': 'cx-product-manager',
56
+ prfaq: 'cx-product-manager',
57
+ 'one-pager': 'cx-product-manager',
58
+ 'backlog-proposal': 'cx-product-manager',
59
+ 'customer-profile': 'cx-product-manager',
60
+ adr: 'cx-architect',
61
+ rfc: 'cx-architect',
62
+ 'rfc-platform': 'cx-architect',
63
+ 'architecture-overview': 'cx-architect',
64
+ 'system-design': 'cx-architect',
65
+ 'research-brief': 'cx-researcher',
66
+ 'evidence-brief': 'cx-researcher',
67
+ 'signal-brief': 'cx-researcher',
68
+ 'product-intelligence-report': 'cx-researcher',
69
+ runbook: 'cx-sre',
70
+ 'incident-report': 'cx-sre',
71
+ postmortem: 'cx-sre',
72
+ 'test-plan': 'cx-qa',
73
+ 'qa-strategy': 'cx-qa',
74
+ 'security-review': 'cx-security',
75
+ 'threat-model': 'cx-security',
76
+ memo: 'cx-docs-keeper',
77
+ changelog: 'cx-docs-keeper',
78
+ };
79
+
80
+ // Event ownership for the role framework (lib/roles/**). When a hook emits
81
+ // an event of one of these types, the gateway routes the invocation to the
82
+ // listed persona. Empty owners mean the event is recorded but no persona
83
+ // fires until onboarded. Keep in sync with agents/role-manifests.json.
84
+
85
+ export const EVENT_OWNERSHIP = {
86
+ 'push_gate.fail': 'cx-sre',
87
+ 'service.down': 'cx-sre',
88
+ 'mcp.unhealthy.persistent': 'cx-sre',
89
+ 'edit_loop.stuck': 'cx-sre',
90
+ 'test.fail': 'cx-qa',
91
+ 'test.flake': 'cx-qa',
92
+ 'coverage.drop': 'cx-qa',
93
+ 'dep.cve': 'cx-security',
94
+ 'secrets.detected': 'cx-security',
95
+ 'config.protection.violation': 'cx-security',
96
+ 'pr.merged.no-docs': 'cx-docs-keeper',
97
+ 'changelog.missing': 'cx-docs-keeper',
98
+ 'readme.stale': 'cx-docs-keeper',
99
+ 'adr.requested': 'cx-architect',
100
+ 'arch.boundary.violated': 'cx-architect',
101
+ 'regression.detected': 'cx-debugger',
102
+ 'hang.detected': 'cx-debugger',
103
+ 'release.candidate': 'cx-release-manager',
104
+ 'version.bump.needed': 'cx-release-manager',
105
+ 'backlog.stale': 'cx-product-manager',
106
+ 'prd.requested': 'cx-product-manager',
107
+ 'pr.opened': 'cx-reviewer',
108
+ 'pr.ready-for-review': 'cx-reviewer',
109
+ 'infra.change.requested': 'cx-platform-engineer',
110
+ 'service.scale.event': 'cx-platform-engineer',
111
+ 'design.requested': 'cx-designer',
112
+ 'a11y.violation': 'cx-accessibility',
113
+ 'research.requested': 'cx-researcher',
114
+ 'evidence.requested': 'cx-researcher',
115
+ 'eval.regression': 'cx-evaluator',
116
+ 'trace.anomaly': 'cx-trace-reviewer',
117
+ 'dep.license': 'cx-legal-compliance',
118
+ 'privacy-policy.review': 'cx-legal-compliance',
119
+ 'strategy.required': 'cx-business-strategist',
120
+ 'plan.requested': 'cx-operations',
121
+ 'research.gate.required': 'cx-rd-lead',
122
+ };
123
+
124
+ const DOC_AUTHORING_PATTERNS = [
125
+ { pattern: /\b(adr|architecture decision record)s?\b/i, docType: 'adr' },
126
+ { pattern: /\bmeta[\s-]prd\b/i, docType: 'meta-prd' },
127
+ { pattern: /\bplatform prd\b/i, docType: 'prd-platform' },
128
+ { pattern: /\bbusiness prd\b/i, docType: 'prd-business' },
129
+ { pattern: /\bprd\b|\bproduct requirements? document/i, docType: 'prd' },
130
+ { pattern: /\bprfaq\b|\bpress release.*faq/i, docType: 'prfaq' },
131
+ { pattern: /\bone[\s-]pager\b/i, docType: 'one-pager' },
132
+ { pattern: /\bbacklog proposal/i, docType: 'backlog-proposal' },
133
+ { pattern: /\bcustomer profile/i, docType: 'customer-profile' },
134
+ { pattern: /\brfc\b|\brequest for comments?\b/i, docType: 'rfc' },
135
+ { pattern: /\barchitecture overview/i, docType: 'architecture-overview' },
136
+ { pattern: /\bsystem design/i, docType: 'system-design' },
137
+ { pattern: /\bresearch brief/i, docType: 'research-brief' },
138
+ { pattern: /\bevidence brief/i, docType: 'evidence-brief' },
139
+ { pattern: /\bsignal brief/i, docType: 'signal-brief' },
140
+ { pattern: /\bproduct intelligence report/i, docType: 'product-intelligence-report' },
141
+ { pattern: /\brunbook/i, docType: 'runbook' },
142
+ { pattern: /\bincident report/i, docType: 'incident-report' },
143
+ { pattern: /\bpostmortem|\bpost[\s-]mortem/i, docType: 'postmortem' },
144
+ { pattern: /\btest plan/i, docType: 'test-plan' },
145
+ { pattern: /\bqa strategy/i, docType: 'qa-strategy' },
146
+ { pattern: /\bthreat model/i, docType: 'threat-model' },
147
+ { pattern: /\bsecurity review/i, docType: 'security-review' },
148
+ { pattern: /\bchangelog/i, docType: 'changelog' },
149
+ ];
150
+
151
+ const AUTHORING_VERBS = /\b(write|writing|draft|drafting|create|creating|author|authoring|produce|producing|compose|composing|prepare|preparing)\b/i;
152
+
153
+ /**
154
+ * Detects whether the request is asking for authorship of a typed document
155
+ * that has a canonical owner. Returns `{ docType, owner }` when matched,
156
+ * or null otherwise.
157
+ */
158
+ export function detectDocAuthoringIntent(request = '') {
159
+ const text = String(request);
160
+ if (!AUTHORING_VERBS.test(text)) return null;
161
+ for (const { pattern, docType } of DOC_AUTHORING_PATTERNS) {
162
+ if (pattern.test(text)) {
163
+ return { docType, owner: DOC_OWNERSHIP[docType] ?? null };
164
+ }
165
+ }
166
+ return null;
167
+ }
168
+
169
+ const PROPER_NOUN_STOPLIST = new Set([
170
+ 'I', 'A', 'An', 'The', 'This', 'That', 'These', 'Those', 'My', 'Our', 'Your',
171
+ 'We', 'They', 'He', 'She', 'It', 'Please', 'Thanks', 'Hi', 'Hello',
172
+ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
173
+ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
174
+ 'September', 'October', 'November', 'December',
175
+ 'Construct', 'Claude', 'GPT', 'OpenAI', 'Anthropic', 'GitHub', 'Google', 'Microsoft',
176
+ ]);
177
+
178
+ /**
179
+ * Extracts proper-noun candidates from a request — capitalized tokens that are
180
+ * not common English words, day/month names, or known product/company names.
181
+ * Detects named entities that likely require external research.
182
+ */
183
+ export function extractNamedEntities(request = '') {
184
+ const text = String(request);
185
+ const tokens = new Set();
186
+ // CamelCase single words (e.g. "ProjectIverson")
187
+ for (const match of text.matchAll(/\b[A-Z][a-z]+[A-Z][A-Za-z]+\b/g)) tokens.add(match[0]);
188
+ // Capitalized multi-word sequences (e.g. "Project Iverson")
189
+ 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]);
190
+ // Single capitalized words not at sentence start
191
+ for (const match of text.matchAll(/(?<=[a-z,;:]\s)[A-Z][a-z]{3,}\b/g)) tokens.add(match[0]);
192
+ return Array.from(tokens).filter((token) => {
193
+ const head = token.split(/\s+/)[0];
194
+ return !PROPER_NOUN_STOPLIST.has(head);
195
+ });
196
+ }
197
+
198
+ /**
199
+ * Returns whether external research is required before scaffolding, with the
200
+ * reason. Triggered by named entities not in the project glossary, or by
201
+ * architecture / writing / research-driven work regardless of entities.
202
+ */
203
+ export function requiresExternalResearch({ request = '', workCategory, riskFlags } = {}) {
204
+ const entities = extractNamedEntities(request);
205
+ const category = workCategory ?? classifyWorkCategory(request);
206
+ const flags = riskFlags ?? detectRiskFlags(request);
207
+ if (entities.length > 0) {
208
+ return { required: true, reason: 'named-entities', entities };
209
+ }
210
+ if (category === WORK_CATEGORIES.writing || flags.architecture || flags.docs) {
211
+ return { required: true, reason: 'writing-or-architecture' };
212
+ }
213
+ // Research intent alone doesn't force external-source research — a simple
214
+ // code walkthrough ("explain how X works") is research intent but doesn't
215
+ // need primary-source citations. Only fire when combined with a broader
216
+ // scope that implies the answer isn't in the immediate code context.
217
+ return { required: false };
218
+ }
219
+
220
+ /**
221
+ * Returns whether the request must pass a framing challenge
222
+ * (cx-devil-advocate or cx-architect problem-reframing) before scaffolding.
223
+ * Fires for architecture work, documentation sets, and any research-driven
224
+ * artifact. Fail-closed: when in doubt, require the challenge.
225
+ */
226
+ export function requiresFramingChallenge({ request = '', workCategory, riskFlags, introducesContract = false } = {}) {
227
+ const category = workCategory ?? classifyWorkCategory(request);
228
+ const flags = riskFlags ?? detectRiskFlags(request);
229
+ if (flags.architecture || introducesContract) return { required: true, reason: 'architecture-or-contract' };
230
+ if (category === WORK_CATEGORIES.writing && flags.docs) return { required: true, reason: 'documentation-set' };
231
+ if (isProductIntelligenceRequest(request)) return { required: true, reason: 'product-intelligence' };
232
+ if (detectDocAuthoringIntent(request)) return { required: true, reason: 'typed-document' };
233
+ return { required: false };
234
+ }
235
+
29
236
  const SPECIALIST_MAP = {
30
237
  implementation: ['cx-engineer'],
31
238
  investigation: ['cx-debugger', 'cx-engineer'],
@@ -125,6 +332,78 @@ export function isDataEngineeringRequest(request = '') {
125
332
  return Boolean(classifyDataEngineerFlavor(request));
126
333
  }
127
334
 
335
+ // Activation detectors for specialists without a flavor classifier. Each maps
336
+ // a request to a keyword signature; an empty match leaves the specialist
337
+ // dormant unless an event in EVENT_OWNERSHIP fires.
338
+
339
+ export function isLegalComplianceRequest(request = '') {
340
+ const text = String(request).toLowerCase();
341
+ return containsAny(text, [
342
+ 'legal review', 'compliance review', 'regulatory', 'gdpr', 'ccpa', 'hipaa',
343
+ 'soc 2', 'soc2', 'data processing agreement', 'dpa', 'terms of service',
344
+ 'license compliance', 'open-source license', 'attribution requirement',
345
+ 'privacy policy', 'consent flow', 'data residency', 'export control',
346
+ ]);
347
+ }
348
+
349
+ export function isBusinessStrategyRequest(request = '') {
350
+ const text = String(request).toLowerCase();
351
+ return containsAny(text, [
352
+ 'go-to-market', 'gtm strategy', 'market positioning', 'competitive analysis',
353
+ 'business case', 'value proposition', 'pricing strategy', 'market segmentation',
354
+ 'investment thesis', 'strategic direction',
355
+ ]);
356
+ }
357
+
358
+ export function isOperationsPlanningRequest(request = '') {
359
+ const text = String(request).toLowerCase();
360
+ return containsAny(text, [
361
+ 'dependency sequencing', 'critical path', 'milestone plan',
362
+ 'resource allocation', 'capacity planning', 'roadmap sequencing',
363
+ 'cross-team dependency', 'multi-quarter plan', 'rollout sequencing',
364
+ ]);
365
+ }
366
+
367
+ export function isRdLeadRequest(request = '') {
368
+ const text = String(request).toLowerCase();
369
+ return containsAny(text, [
370
+ 'hypothesis', 'falsifiable', 'research question', 'experimental design',
371
+ 'technology spike', 'feasibility study', 'proof of concept', 'r&d',
372
+ ]);
373
+ }
374
+
375
+ export function isExplorerRequest(request = '') {
376
+ const text = String(request).toLowerCase();
377
+ return containsAny(text, [
378
+ 'explore the', 'spike', 'walkthrough', 'code walk', 'scoping pass',
379
+ 'recon', 'reconnaissance', 'survey the code', 'orient me',
380
+ ]);
381
+ }
382
+
383
+ export function isVisualDeliverableRequest(request = '') {
384
+ const text = String(request).toLowerCase();
385
+ return containsAny(text, [
386
+ 'wireframe',
387
+ 'diagram',
388
+ 'flowchart',
389
+ 'mermaid',
390
+ 'sequence diagram',
391
+ 'state diagram',
392
+ 'er diagram',
393
+ 'mockup',
394
+ 'storyboard',
395
+ 'deck',
396
+ 'slide deck',
397
+ 'slides',
398
+ 'presentation',
399
+ 'powerpoint',
400
+ 'ppt',
401
+ 'pptx',
402
+ 'walkthrough video',
403
+ 'demo video',
404
+ ]);
405
+ }
406
+
128
407
  export function classifyRoleFlavors(request = '') {
129
408
  return {
130
409
  architect: classifyArchitectFlavor(request),
@@ -160,7 +439,7 @@ export function classifyIntent(request = '') {
160
439
 
161
440
  export function classifyWorkCategory(request = '', riskFlags = detectRiskFlags(request)) {
162
441
  const text = String(request).toLowerCase();
163
- if (riskFlags.ui) return WORK_CATEGORIES.visual;
442
+ if (riskFlags.ui || isVisualDeliverableRequest(text)) return WORK_CATEGORIES.visual;
164
443
  if (riskFlags.docs || containsAny(text, ['write', 'rewrite', 'document', 'spec', 'requirements'])) return WORK_CATEGORIES.writing;
165
444
  if (includesAny(text, [/analy[sz]e/, /measure/, /metrics/, /score/, /evaluate/])) return WORK_CATEGORIES.analysis;
166
445
  if (riskFlags.architecture || riskFlags.ai || includesAny(text, [/plan/, /strategy/, /system/, /refactor/, /orchestr/])) return WORK_CATEGORIES.deep;
@@ -176,25 +455,44 @@ export function determineExecutionTrack({
176
455
  riskFlags = detectRiskFlags(request),
177
456
  } = {}) {
178
457
  const intent = classifyIntent(request);
458
+ const workCategory = classifyWorkCategory(request, riskFlags);
179
459
  if (explicitDrive) return EXECUTION_TRACKS.orchestrated;
180
460
  if (intent === INTENT_CLASSES.research && fileCount <= 1 && moduleCount <= 1) return EXECUTION_TRACKS.immediate;
181
461
  if (introducesContract || fileCount >= 3 || moduleCount >= 2) return EXECUTION_TRACKS.orchestrated;
182
462
  if (riskFlags.architecture || riskFlags.security || riskFlags.dataIntegrity || riskFlags.ai) return EXECUTION_TRACKS.orchestrated;
463
+ if (workCategory === WORK_CATEGORIES.visual) return EXECUTION_TRACKS.focused;
464
+ // A keyword that names a specialist should activate that specialist even
465
+ // for small-scope work — otherwise the immediate-track fallback below
466
+ // silently drops the request to "answer directly," losing the specialty.
467
+ if (
468
+ isLegalComplianceRequest(request)
469
+ || isBusinessStrategyRequest(request)
470
+ || isOperationsPlanningRequest(request)
471
+ || isRdLeadRequest(request)
472
+ || isExplorerRequest(request)
473
+ ) {
474
+ return EXECUTION_TRACKS.focused;
475
+ }
183
476
  if (fileCount <= 1 && moduleCount <= 1 && !includesAny(String(request).toLowerCase(), [/end to end/, /ship/, /full/])) return EXECUTION_TRACKS.immediate;
184
477
  return EXECUTION_TRACKS.focused;
185
478
  }
186
479
 
187
- export function selectSpecialists({ request = '', intent, track, riskFlags = detectRiskFlags(request) } = {}) {
480
+ export function selectSpecialists({ request = '', intent, track, riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
188
481
  const text = String(request).toLowerCase();
189
482
  const productRequest = isProductIntelligenceRequest(text);
190
483
  const dataAnalysisRequest = isDataAnalysisRequest(text);
191
484
  const dataEngineeringRequest = isDataEngineeringRequest(text);
192
485
  if (track === EXECUTION_TRACKS.immediate) return [];
193
486
  if (track === EXECUTION_TRACKS.focused) {
194
- if (riskFlags.ui) return ['cx-designer'];
487
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) return ['cx-designer'];
195
488
  if (productRequest) return ['cx-product-manager'];
196
489
  if (dataEngineeringRequest) return ['cx-data-engineer'];
197
490
  if (dataAnalysisRequest) return ['cx-data-analyst'];
491
+ if (isLegalComplianceRequest(text)) return ['cx-legal-compliance'];
492
+ if (isBusinessStrategyRequest(text)) return ['cx-business-strategist'];
493
+ if (isOperationsPlanningRequest(text)) return ['cx-operations'];
494
+ if (isRdLeadRequest(text)) return ['cx-rd-lead'];
495
+ if (isExplorerRequest(text)) return ['cx-explorer'];
198
496
  if (riskFlags.docs) return ['cx-docs-keeper'];
199
497
  if (riskFlags.security && intent === INTENT_CLASSES.evaluation) return ['cx-security'];
200
498
  return SPECIALIST_MAP[intent] || ['cx-engineer'];
@@ -203,7 +501,7 @@ export function selectSpecialists({ request = '', intent, track, riskFlags = det
203
501
  const specialists = ['cx-architect'];
204
502
  if (intent === INTENT_CLASSES.fix || intent === INTENT_CLASSES.investigation) specialists.push('cx-debugger');
205
503
  if (intent === INTENT_CLASSES.research) specialists.push('cx-researcher');
206
- if (riskFlags.ui) specialists.push('cx-designer');
504
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) specialists.push('cx-designer');
207
505
  if (productRequest) specialists.push('cx-product-manager');
208
506
  if (dataAnalysisRequest) specialists.push('cx-data-analyst');
209
507
  if (dataEngineeringRequest) specialists.push('cx-data-engineer');
@@ -213,6 +511,43 @@ export function selectSpecialists({ request = '', intent, track, riskFlags = det
213
511
  return Array.from(new Set(specialists));
214
512
  }
215
513
 
514
+ /**
515
+ * Identify specialists that can run in parallel with implementation.
516
+ * Returns an array of specialist names that should execute concurrently
517
+ * rather than sequentially. This reduces latency for independent checks.
518
+ *
519
+ * Parallel execution is safe when:
520
+ * - The specialist only reads/analyzes (doesn't mutate)
521
+ * - Their output doesn't depend on implementation completing first
522
+ * - They provide early feedback that can shape implementation
523
+ */
524
+ export function identifyParallelChecks({ request = '', riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
525
+ const parallelChecks = [];
526
+ const text = String(request).toLowerCase();
527
+
528
+ // Security review can run parallel to implementation for early threat detection
529
+ if (riskFlags.security || containsAny(text, ['auth', 'permission', 'secret', 'payment', 'pii'])) {
530
+ parallelChecks.push('cx-security');
531
+ }
532
+
533
+ // Accessibility review can run parallel for UI work
534
+ if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui || containsAny(text, ['ui', 'ux', 'interface', 'component'])) {
535
+ parallelChecks.push('cx-accessibility');
536
+ }
537
+
538
+ // Performance review for data-intensive operations
539
+ if (containsAny(text, ['performance', 'latency', 'throughput', 'scale', 'optimization'])) {
540
+ parallelChecks.push('cx-sre');
541
+ }
542
+
543
+ // Legal compliance for regulated domains
544
+ if (isLegalComplianceRequest(request)) {
545
+ parallelChecks.push('cx-legal-compliance');
546
+ }
547
+
548
+ return parallelChecks;
549
+ }
550
+
216
551
  export function requiresExecutiveApproval({
217
552
  scopeChange = false,
218
553
  productDecision = false,
@@ -223,6 +558,94 @@ export function requiresExecutiveApproval({
223
558
  return Boolean(scopeChange || productDecision || riskAcceptance || irreversibleAction || blockedDependency);
224
559
  }
225
560
 
561
+ // Structured request signals computed once and fed to both selectSpecialists
562
+ // and the proactive-trigger pass. Centralises every cheap signal so specialists
563
+ // don't each re-derive them from raw text. Returned shape is stable — additive
564
+ // only.
565
+
566
+ export function requestSignals(request = '', context = {}) {
567
+ const text = String(request).toLowerCase();
568
+ const riskFlags = detectRiskFlags(request);
569
+ const intent = classifyIntent(request);
570
+ const workCategory = classifyWorkCategory(request, riskFlags);
571
+
572
+ const ambiguityIndicators = [
573
+ 'maybe', 'something like', 'figure out', 'somehow', 'sort of', 'kind of', 'idea', 'rough', 'tbd',
574
+ 'we should probably', 'not sure', 'thinking about',
575
+ ];
576
+ const namedConstraintIndicators = [
577
+ 'must not', 'cannot exceed', 'has to', 'deadline', 'budget', 'within', 'no later than',
578
+ 'maximum', 'minimum', 'sla', 'p95', 'p99',
579
+ ];
580
+ const blastRadiusWide = [
581
+ 'all users', 'every user', 'global', 'org-wide', 'company-wide', 'breaking change',
582
+ 'migration', 'mass update', 'backfill', 'destructive',
583
+ ];
584
+ const blastRadiusMedium = [
585
+ 'feature flag', 'beta cohort', 'experiment', 'rollout', 'shadow',
586
+ ];
587
+
588
+ const ambiguityHits = ambiguityIndicators.filter((kw) => text.includes(kw)).length;
589
+ const ambiguityScore = Math.min(1, ambiguityHits / 3);
590
+
591
+ let blastRadius = 'narrow';
592
+ if (containsAny(text, blastRadiusWide)) blastRadius = 'wide';
593
+ else if (containsAny(text, blastRadiusMedium)) blastRadius = 'medium';
594
+
595
+ return {
596
+ intent,
597
+ workCategory,
598
+ riskFlags,
599
+ ambiguityScore,
600
+ hasSuccessMetric: containsAny(text, ['success metric', 'kpi', 'target metric', 'goal metric', 'acceptance criteria']),
601
+ hasNamedConstraints: containsAny(text, namedConstraintIndicators),
602
+ blastRadius,
603
+ authOrPayments: containsAny(text, ['auth', 'authentication', 'authorization', 'payment', 'pii', 'personal data', 'compliance']),
604
+ visualDeliverable: isVisualDeliverableRequest(text),
605
+ namedUsers: context?.namedUsers || [],
606
+ };
607
+ }
608
+
609
+ // Signal-driven proactive triggers. Returns a list of { specialist, reason }
610
+ // pairs for specialists that should engage PRE-DISPATCH based on signals —
611
+ // separate from the keyword-only paths in selectSpecialists. Empty when no
612
+ // signal trips.
613
+
614
+ export function proactiveTriggers(signals) {
615
+ const triggers = [];
616
+ const { ambiguityScore, workCategory, authOrPayments, riskFlags, blastRadius, hasSuccessMetric, visualDeliverable } = signals;
617
+
618
+ if (ambiguityScore > 0.5 && workCategory === WORK_CATEGORIES.deep) {
619
+ triggers.push({ specialist: 'cx-product-manager', reason: 'clarify acceptance criteria (high ambiguity)' });
620
+ }
621
+ if (visualDeliverable || riskFlags.ui) {
622
+ triggers.push({ specialist: 'cx-designer', reason: 'visual deliverable / UI risk surface' });
623
+ }
624
+ if (authOrPayments && blastRadius !== 'narrow') {
625
+ triggers.push({ specialist: 'cx-security', reason: 'pre-dispatch threat model (auth/payments/PII + non-narrow blast radius)' });
626
+ }
627
+ if (riskFlags.architecture && !hasSuccessMetric) {
628
+ triggers.push({ specialist: 'cx-devil-advocate', reason: 'architecture change without stated success metric' });
629
+ }
630
+ if (blastRadius === 'wide') {
631
+ triggers.push({ specialist: 'cx-sre', reason: 'wide blast radius — operational readiness review' });
632
+ }
633
+ return triggers;
634
+ }
635
+
636
+ // User-visible dispatch summary: one line per specialist with the reason it
637
+ // was engaged. Replaces the terse one-line dispatchPlan when callers want to
638
+ // surface the routing decision before starting work.
639
+
640
+ export function buildDispatchSummary({ specialists = [], reasons = {} } = {}) {
641
+ if (specialists.length === 0) return 'Engaging: (none — responding directly).';
642
+ const lines = specialists.map((name) => {
643
+ const reason = reasons[name];
644
+ return reason ? ` - ${name} (${reason})` : ` - ${name}`;
645
+ });
646
+ return `Engaging:\n${lines.join('\n')}`;
647
+ }
648
+
226
649
  export function buildDispatchPlan({ track, intent, specialists = [] }) {
227
650
  if (track === EXECUTION_TRACKS.immediate) return 'Plan: respond directly.';
228
651
  if (track === EXECUTION_TRACKS.focused) return `Plan: ${specialists.join(' → ')}.`;
@@ -244,8 +667,62 @@ export function routeRequest(options = {}) {
244
667
  : null;
245
668
  const workCategory = classifyWorkCategory(options.request, riskFlags);
246
669
  const track = determineExecutionTrack({ ...options, riskFlags });
247
- const specialists = selectSpecialists({ ...options, intent, track, riskFlags });
248
- return {
670
+ let specialists = selectSpecialists({ ...options, intent, track, riskFlags, workCategory });
671
+ const docAuthoring = detectDocAuthoringIntent(options.request);
672
+ const externalResearch = requiresExternalResearch({ request: options.request, workCategory, riskFlags });
673
+ const framingChallenge = requiresFramingChallenge({ request: options.request, workCategory, riskFlags, introducesContract: options.introducesContract });
674
+
675
+ // Doc ownership: the owning specialist authors; orchestrator routes.
676
+ if (docAuthoring?.owner && !specialists.includes(docAuthoring.owner)) {
677
+ specialists = [docAuthoring.owner, ...specialists];
678
+ }
679
+ // External research must precede authoring.
680
+ if (externalResearch.required && !specialists.includes('cx-researcher')) {
681
+ specialists = ['cx-researcher', ...specialists];
682
+ }
683
+ // Framing challenge must precede scaffolding.
684
+ if (framingChallenge.required && !specialists.includes('cx-devil-advocate')) {
685
+ specialists = ['cx-devil-advocate', ...specialists];
686
+ }
687
+ // Pre-dispatch specialists that surface concerns before architecture locks in.
688
+ // Order matters: business framing → R&D validation → legal/compliance review,
689
+ // each prepended so it runs earliest in the chain.
690
+ if (isLegalComplianceRequest(options.request) && !specialists.includes('cx-legal-compliance')) {
691
+ specialists = ['cx-legal-compliance', ...specialists];
692
+ }
693
+ if (isRdLeadRequest(options.request) && !specialists.includes('cx-rd-lead')) {
694
+ specialists = ['cx-rd-lead', ...specialists];
695
+ }
696
+ if (isBusinessStrategyRequest(options.request) && !specialists.includes('cx-business-strategist')) {
697
+ specialists = ['cx-business-strategist', ...specialists];
698
+ }
699
+
700
+ // Signal-driven proactive triggers — surfaces specialists whose engagement
701
+ // hinges on structured signals rather than literal keyword matches. The
702
+ // routing list is union'd with the keyword paths so neither dimension
703
+ // drops a legitimate dispatch.
704
+
705
+ const signals = requestSignals(options.request, options.context);
706
+ const triggers = proactiveTriggers(signals);
707
+ const reasons = {};
708
+ for (const { specialist, reason } of triggers) {
709
+ if (!specialists.includes(specialist)) specialists = [specialist, ...specialists];
710
+ reasons[specialist] = reason;
711
+ }
712
+ specialists = Array.from(new Set(specialists));
713
+
714
+ const contractChain = resolveContractChain({
715
+ intent,
716
+ workCategory,
717
+ track,
718
+ riskFlags,
719
+ specialists,
720
+ framingChallenge,
721
+ externalResearch,
722
+ docAuthoring,
723
+ });
724
+
725
+ const route = {
249
726
  intent,
250
727
  workCategory,
251
728
  track,
@@ -253,6 +730,32 @@ export function routeRequest(options = {}) {
253
730
  productFlavor,
254
731
  roleFlavors,
255
732
  specialists,
733
+ signals,
734
+ triggers,
735
+ dispatchReasons: reasons,
736
+ docAuthoring,
737
+ externalResearch,
738
+ framingChallenge,
739
+ contractChain,
256
740
  dispatchPlan: buildDispatchPlan({ track, intent, specialists }),
741
+ dispatchSummary: buildDispatchSummary({ specialists, reasons }),
257
742
  };
743
+
744
+ if (process.env.CONSTRUCT_VERBOSE === '1' && triggers.length > 0) {
745
+ for (const { specialist, reason } of triggers) {
746
+ console.error(`[route] proactive: ${specialist} (${reason})`);
747
+ }
748
+ }
749
+
750
+ return route;
751
+ }
752
+
753
+ // Sync wrapper that returns the keyword route immediately and fires the
754
+ // LLM intent verifier in the background. The verifier writes its verdict
755
+ // to ~/.cx/intent-verifications.jsonl for offline tuning; the dispatched
756
+ // route never waits on a model round-trip. Disable the background call
757
+ // entirely with CONSTRUCT_INTENT_VERIFY=off — see lib/intent-classifier.mjs.
758
+ export function routeRequestVerified(options = {}) {
759
+ const route = routeRequest(options);
760
+ return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
258
761
  }