@geraldmaron/construct 1.0.0 → 1.0.2
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.
- package/.env.example +9 -6
- package/LICENSE +201 -21
- package/README.md +132 -257
- package/agents/contracts.json +387 -0
- package/agents/prompts/cx-accessibility.md +8 -0
- package/agents/prompts/cx-ai-engineer.md +92 -0
- package/agents/prompts/cx-architect.md +10 -2
- package/agents/prompts/cx-business-strategist.md +13 -0
- package/agents/prompts/cx-data-analyst.md +80 -0
- package/agents/prompts/cx-data-engineer.md +4 -0
- package/agents/prompts/cx-debugger.md +8 -0
- package/agents/prompts/cx-designer.md +19 -0
- package/agents/prompts/cx-devil-advocate.md +4 -0
- package/agents/prompts/cx-docs-keeper.md +128 -0
- package/agents/prompts/cx-engineer.md +13 -0
- package/agents/prompts/cx-evaluator.md +4 -0
- package/agents/prompts/cx-explorer.md +12 -0
- package/agents/prompts/cx-legal-compliance.md +13 -0
- package/agents/prompts/cx-operations.md +13 -0
- package/agents/prompts/cx-orchestrator.md +107 -4
- package/agents/prompts/cx-platform-engineer.md +75 -0
- package/agents/prompts/cx-product-manager.md +8 -0
- package/agents/prompts/cx-qa.md +100 -0
- package/agents/prompts/cx-rd-lead.md +13 -0
- package/agents/prompts/cx-release-manager.md +8 -0
- package/agents/prompts/cx-researcher.md +21 -1
- package/agents/prompts/cx-reviewer.md +8 -0
- package/agents/prompts/cx-security.md +104 -0
- package/agents/prompts/cx-sre.md +104 -0
- package/agents/prompts/cx-test-automation.md +4 -0
- package/agents/prompts/cx-trace-reviewer.md +7 -3
- package/agents/prompts/cx-ux-researcher.md +4 -0
- package/agents/registry.json +365 -90
- package/agents/role-manifests.json +217 -0
- package/bin/construct +3345 -141
- package/bin/construct-postinstall.mjs +87 -0
- package/commands/build/feature.md +6 -6
- package/commands/plan/feature.md +6 -5
- package/commands/plan/requirements.md +1 -1
- package/commands/ship/status.md +1 -1
- package/commands/work/drive.md +3 -3
- package/commands/work/optimize-prompts.md +3 -3
- package/db/{migrations → schema}/001_init.sql +2 -0
- package/db/schema/002_pgvector.sql +182 -0
- package/db/schema/003_intake.sql +47 -0
- package/examples/README.md +85 -0
- package/examples/internal/roles/architect/bad/clever-plan-without-contracts.md +30 -0
- package/examples/internal/roles/architect/golden/explicit-tradeoff-before-plan.md +23 -0
- package/examples/internal/roles/engineer/bad/speculative-abstraction.md +30 -0
- package/examples/internal/roles/engineer/golden/read-before-write.md +28 -0
- package/examples/internal/roles/orchestrator/bad/everything-becomes-multi-agent.md +29 -0
- package/examples/internal/roles/orchestrator/golden/minimal-dispatch.md +22 -0
- package/examples/internal/roles/qa/bad/coverage-theater.md +30 -0
- package/examples/internal/roles/qa/golden/regression-gate.md +23 -0
- package/examples/internal/roles/reviewer/bad/lgtm-without-verification.md +29 -0
- package/examples/internal/roles/reviewer/golden/find-structural-risk-first.md +28 -0
- package/examples/personas/construct/adversarial/ignore-instruction-to-skip-approval.md +22 -0
- package/examples/personas/construct/bad/commit-without-approval.md +30 -0
- package/examples/personas/construct/boundary/blocked-needs-main-input.md +29 -0
- package/examples/personas/construct/golden/branch-approval-before-mutation.md +36 -0
- package/examples/personas/construct/golden/focused-direct-answer.md +28 -0
- package/examples/provider-plugin/README.md +34 -0
- package/examples/provider-plugin/index.mjs +74 -0
- package/examples/provider-plugin/package.json +15 -0
- package/examples/seed-observations/README.md +38 -0
- package/examples/seed-observations/anti-patterns.md +44 -0
- package/examples/seed-observations/decisions.md +36 -0
- package/examples/seed-observations/patterns.md +42 -0
- package/lib/agent-contracts-enforce.mjs +158 -0
- package/lib/agent-contracts.mjs +231 -0
- package/lib/agents/postconditions.mjs +126 -0
- package/lib/agents/schema.mjs +124 -0
- package/lib/artifact-capture.mjs +183 -0
- package/lib/audit-trail.mjs +149 -0
- package/lib/auto-docs.mjs +245 -65
- package/lib/beads/auto-close.mjs +126 -0
- package/lib/beads/drift.mjs +171 -0
- package/lib/beads-automation.mjs +542 -0
- package/lib/beads-client.mjs +518 -0
- package/lib/beads-lock.mjs +377 -0
- package/lib/beads-optimistic.mjs +365 -0
- package/lib/bootstrap/built-ins.mjs +136 -0
- package/lib/bootstrap/lazy-install.mjs +161 -0
- package/lib/bootstrap/resources.mjs +120 -0
- package/lib/bootstrap.mjs +105 -0
- package/lib/cache-governor.js +213 -0
- package/lib/cache-strategy-anthropic.js +62 -0
- package/lib/cache-strategy-google.js +79 -0
- package/lib/cache-strategy-none.js +30 -0
- package/lib/cache-strategy-openai.js +48 -0
- package/lib/cache-strategy.js +91 -0
- package/lib/claude-allow.mjs +149 -0
- package/lib/cli-commands.mjs +413 -258
- package/lib/codex-config.mjs +3 -1
- package/lib/comment-lint.mjs +55 -6
- package/lib/completions.mjs +21 -6
- package/lib/config/alias.mjs +56 -0
- package/lib/config/project-config.mjs +335 -0
- package/lib/config/schema.mjs +159 -0
- package/lib/context-router.mjs +308 -0
- package/lib/cost-ledger.mjs +177 -0
- package/lib/cost.mjs +171 -10
- package/lib/dashboard-static.mjs +158 -0
- package/lib/deployment-mode.mjs +86 -0
- package/lib/deprecate.mjs +49 -0
- package/lib/dispatch-batch.js +183 -0
- package/lib/distill.mjs +21 -8
- package/lib/doc-stamp.mjs +164 -0
- package/lib/doc-verify.mjs +119 -0
- package/lib/docs-routing.mjs +89 -0
- package/lib/docs-verify.mjs +417 -0
- package/lib/doctor/audit.mjs +71 -0
- package/lib/doctor/cli.mjs +99 -0
- package/lib/doctor/escalate.mjs +29 -0
- package/lib/doctor/index.mjs +140 -0
- package/lib/doctor/report.mjs +170 -0
- package/lib/doctor/watchers/bd-watch.mjs +117 -0
- package/lib/doctor/watchers/cost.mjs +130 -0
- package/lib/doctor/watchers/disk.mjs +122 -0
- package/lib/doctor/watchers/handoffs.mjs +33 -0
- package/lib/doctor/watchers/process-pressure.mjs +60 -0
- package/lib/doctor/watchers/service-health.mjs +188 -0
- package/lib/document-extract.mjs +288 -0
- package/lib/document-ingest.mjs +230 -0
- package/lib/drop.mjs +282 -0
- package/lib/embed/approval-queue.mjs +176 -0
- package/lib/embed/artifact.mjs +349 -0
- package/lib/embed/authority-guard.mjs +155 -0
- package/lib/embed/cli.mjs +408 -0
- package/lib/embed/config.mjs +355 -0
- package/lib/embed/conflict-detection.mjs +264 -0
- package/lib/embed/customer-profiles.mjs +480 -0
- package/lib/embed/daemon.mjs +1309 -0
- package/lib/embed/demand-fetch.mjs +449 -0
- package/lib/embed/docs-lifecycle.mjs +349 -0
- package/lib/embed/inbox-live-watcher.mjs +119 -0
- package/lib/embed/inbox.mjs +343 -0
- package/lib/embed/intake-metrics.mjs +190 -0
- package/lib/embed/jobs/vector-sync.mjs +198 -0
- package/lib/embed/notifications.mjs +75 -0
- package/lib/embed/output.mjs +79 -0
- package/lib/embed/providers/github.mjs +295 -0
- package/lib/embed/providers/jira.mjs +192 -0
- package/lib/embed/providers/linear.mjs +186 -0
- package/lib/embed/providers/registry.mjs +115 -0
- package/lib/embed/providers/slack.mjs +203 -0
- package/lib/embed/recommendation-store.mjs +378 -0
- package/lib/embed/roadmap.mjs +374 -0
- package/lib/embed/role-framing.mjs +110 -0
- package/lib/embed/scheduler.mjs +99 -0
- package/lib/embed/semantic.mjs +325 -0
- package/lib/embed/snapshot.mjs +191 -0
- package/lib/embed/supervision.mjs +235 -0
- package/lib/embed/target-resolver.mjs +186 -0
- package/lib/embed/worker.mjs +62 -0
- package/lib/embed/workspaces.mjs +297 -0
- package/lib/engine/chunker-headings.mjs +110 -0
- package/lib/engine/compressor-heuristic.mjs +100 -0
- package/lib/engine/consolidate.mjs +287 -0
- package/lib/engine/contracts.mjs +126 -0
- package/lib/engine/defaults.mjs +129 -0
- package/lib/engine/eval-retrieval.mjs +148 -0
- package/lib/engine/fuser-rrf.mjs +62 -0
- package/lib/engine/index.mjs +37 -0
- package/lib/engine/registry.mjs +146 -0
- package/lib/engine/reranker-mmr.mjs +90 -0
- package/lib/engine/tokens.mjs +77 -0
- package/lib/entity-store.mjs +280 -0
- package/lib/env-config.mjs +69 -4
- package/lib/evals/retrieval-bench.mjs +159 -0
- package/lib/evaluator-optimizer.mjs +317 -0
- package/lib/features.mjs +159 -29
- package/lib/gates-audit.mjs +236 -0
- package/lib/git-hooks/prepare-commit-msg +58 -0
- package/lib/handoffs/cleanup.mjs +159 -0
- package/lib/handoffs/contract.mjs +162 -0
- package/lib/handoffs/inventory.mjs +120 -0
- package/lib/headhunt.mjs +28 -47
- package/lib/health-check.mjs +399 -0
- package/lib/hook-health.mjs +442 -0
- package/lib/hooks/_lib/log.mjs +82 -0
- package/lib/hooks/adaptive-lint.mjs +26 -2
- package/lib/hooks/agent-tracker.mjs +171 -14
- package/lib/hooks/audit-reads.mjs +109 -0
- package/lib/hooks/audit-trail.mjs +153 -0
- package/lib/hooks/bash-output-logger.mjs +71 -0
- package/lib/hooks/block-no-verify.mjs +41 -0
- package/lib/hooks/ci-status-check.mjs +82 -0
- package/lib/hooks/comment-lint.mjs +29 -7
- package/lib/hooks/config-protection.mjs +39 -18
- package/lib/hooks/context-watch.mjs +137 -0
- package/lib/hooks/context-window-recovery.mjs +4 -20
- package/lib/hooks/dep-audit.mjs +16 -0
- package/lib/hooks/doc-coupling-check.mjs +80 -0
- package/lib/hooks/edit-accumulator.mjs +3 -0
- package/lib/hooks/edit-error-recovery.mjs +3 -0
- package/lib/hooks/edit-guard.mjs +45 -4
- package/lib/hooks/env-check.mjs +3 -0
- package/lib/hooks/guard-bash.mjs +58 -1
- package/lib/hooks/mcp-audit.mjs +18 -20
- package/lib/hooks/mcp-health-check.mjs +36 -0
- package/lib/hooks/model-fallback.mjs +42 -56
- package/lib/hooks/policy-engine.mjs +209 -0
- package/lib/hooks/post-merge-docs-check.mjs +63 -0
- package/lib/hooks/pre-compact.mjs +4 -24
- package/lib/hooks/pre-push-gate.mjs +200 -37
- package/lib/hooks/proactive-activation.mjs +284 -0
- package/lib/hooks/read-tracker.mjs +27 -4
- package/lib/hooks/readme-age-check.mjs +77 -0
- package/lib/hooks/registry-sync.mjs +12 -5
- package/lib/hooks/scan-secrets.mjs +50 -7
- package/lib/hooks/session-optimize.mjs +311 -0
- package/lib/hooks/session-start.mjs +287 -28
- package/lib/hooks/stop-notify.mjs +236 -96
- package/lib/hooks/stop-typecheck.mjs +13 -1
- package/lib/hooks/test-watch.mjs +68 -0
- package/lib/host-capabilities.mjs +31 -10
- package/lib/init-docs.mjs +731 -291
- package/lib/init-unified.mjs +1000 -0
- package/lib/init-update.mjs +168 -0
- package/lib/init.mjs +107 -0
- package/lib/install/first-invocation.mjs +119 -0
- package/lib/install/stage-project.mjs +69 -0
- package/lib/intake/classify.mjs +278 -0
- package/lib/intake/feedback.mjs +273 -0
- package/lib/intake/filesystem-queue.mjs +158 -0
- package/lib/intake/intake-config.mjs +132 -0
- package/lib/intake/postgres-queue.mjs +198 -0
- package/lib/intake/prepare.mjs +139 -0
- package/lib/intake/queue.mjs +83 -0
- package/lib/intake/session-prelude.mjs +50 -0
- package/lib/integrations/intake-integrations.mjs +740 -0
- package/lib/intent-classifier.mjs +253 -0
- package/lib/knowledge/layout.mjs +72 -0
- package/lib/knowledge/rag.mjs +331 -0
- package/lib/knowledge/search.mjs +315 -0
- package/lib/knowledge/trends.mjs +261 -0
- package/lib/logger.mjs +85 -0
- package/lib/mcp/broker.mjs +124 -0
- package/lib/mcp/server.mjs +554 -854
- package/lib/mcp/tools/document.mjs +132 -0
- package/lib/mcp/tools/memory.mjs +209 -0
- package/lib/mcp/tools/project.mjs +349 -0
- package/lib/mcp/tools/skills.mjs +409 -0
- package/lib/mcp/tools/storage.mjs +60 -0
- package/lib/mcp/tools/telemetry.mjs +315 -0
- package/lib/mcp/tools/workflow.mjs +112 -0
- package/lib/mcp-catalog.json +54 -3
- package/lib/mcp-manager.mjs +96 -48
- package/lib/mcp-platform-config.mjs +22 -22
- package/lib/memory-stats.mjs +121 -0
- package/lib/mode-commands.mjs +124 -0
- package/lib/model-free-selector.mjs +184 -0
- package/lib/model-pricing.mjs +152 -0
- package/lib/model-registry.mjs +226 -0
- package/lib/model-router.mjs +362 -386
- package/lib/observation-store.mjs +391 -0
- package/lib/ollama-manager.mjs +428 -0
- package/lib/opencode-config.mjs +13 -3
- package/lib/opencode-runtime-plugin.mjs +70 -38
- package/lib/opencode-telemetry.mjs +64 -6
- package/lib/orchestration-policy.mjs +509 -6
- package/lib/overrides/resolver.mjs +207 -0
- package/lib/parity.mjs +147 -0
- package/lib/paths.mjs +33 -0
- package/lib/performance/generate.mjs +212 -0
- package/lib/plugin-registry.mjs +268 -0
- package/lib/policy/engine.mjs +130 -0
- package/lib/policy/unified-gates.mjs +96 -0
- package/lib/project-detection.mjs +129 -0
- package/lib/project-init-shared.mjs +272 -0
- package/lib/project-profile.mjs +447 -0
- package/lib/prompt-composer.js +434 -0
- package/lib/prompt-metadata.mjs +1 -1
- package/lib/provider-capabilities-anthropic.js +44 -0
- package/lib/provider-capabilities-deepseek.js +37 -0
- package/lib/provider-capabilities-generic.js +26 -0
- package/lib/provider-capabilities-google.js +47 -0
- package/lib/provider-capabilities-openai.js +45 -0
- package/lib/provider-capabilities.js +142 -0
- package/lib/providers/atlassian-confluence/index.mjs +103 -0
- package/lib/providers/atlassian-jira/index.mjs +100 -0
- package/lib/providers/auth-manager.mjs +126 -0
- package/lib/providers/circuit-breaker.mjs +124 -0
- package/lib/providers/contract.mjs +93 -0
- package/lib/providers/github/index.mjs +126 -0
- package/lib/providers/registry.mjs +184 -0
- package/lib/providers/salesforce/index.mjs +100 -0
- package/lib/providers/slack/index.mjs +80 -0
- package/lib/reflect.mjs +137 -0
- package/lib/research-lint.mjs +164 -0
- package/lib/resources/budget.mjs +259 -0
- package/lib/role-preload.mjs +21 -6
- package/lib/roles/approval-surface.mjs +54 -0
- package/lib/roles/cli.mjs +118 -0
- package/lib/roles/event-bus.mjs +79 -0
- package/lib/roles/fence.mjs +84 -0
- package/lib/roles/gateway.mjs +260 -0
- package/lib/roles/hook-emit.mjs +37 -0
- package/lib/roles/manifest.mjs +48 -0
- package/lib/roles/router.mjs +27 -0
- package/lib/runtime-pressure.mjs +360 -0
- package/lib/schema-artifact.mjs +134 -0
- package/lib/schema-infer.mjs +551 -0
- package/lib/server/auth.mjs +168 -0
- package/lib/server/chat.mjs +336 -0
- package/lib/server/cors.mjs +77 -0
- package/lib/server/csrf.mjs +91 -0
- package/lib/server/index.mjs +1927 -78
- package/lib/server/insights.mjs +765 -0
- package/lib/server/rate-limit.mjs +91 -0
- package/lib/server/static/assets/index-ab25c707.js +70 -0
- package/lib/server/static/assets/index-f0c80a2b.css +1 -0
- package/lib/server/static/index.html +12 -817
- package/lib/server/telemetry-login.mjs +108 -0
- package/lib/server/webhook.mjs +510 -0
- package/lib/service-manager.mjs +522 -58
- package/lib/services/pattern-promotion-service.mjs +167 -0
- package/lib/services/telemetry-backend.mjs +178 -0
- package/lib/session-store.mjs +374 -0
- package/lib/setup-prompts.mjs +96 -0
- package/lib/setup.mjs +523 -36
- package/lib/skills-apply.mjs +280 -0
- package/lib/skills-scope.mjs +118 -0
- package/lib/status.mjs +261 -70
- package/lib/storage/admin.mjs +355 -0
- package/lib/storage/backend.mjs +2 -1
- package/lib/storage/backup.mjs +347 -0
- package/lib/storage/embeddings-engine.mjs +133 -0
- package/lib/storage/embeddings-legacy.mjs +85 -0
- package/lib/storage/embeddings-local.mjs +108 -0
- package/lib/storage/embeddings-ollama.mjs +78 -0
- package/lib/storage/embeddings-openai.mjs +85 -0
- package/lib/storage/embeddings.mjs +92 -33
- package/lib/storage/file-lock.mjs +130 -0
- package/lib/storage/fusion.mjs +95 -0
- package/lib/storage/hybrid-query.mjs +34 -27
- package/lib/storage/migrations.mjs +187 -0
- package/lib/storage/postgres-backup.mjs +124 -0
- package/lib/storage/sql-store.mjs +5 -15
- package/lib/storage/state-source.mjs +12 -13
- package/lib/storage/store-version.mjs +115 -0
- package/lib/storage/sync.mjs +144 -35
- package/lib/storage/unified-storage.mjs +550 -0
- package/lib/storage/vector-client.mjs +286 -0
- package/lib/storage/vector-store.mjs +71 -30
- package/lib/task-graph/generate.mjs +135 -0
- package/lib/task-graph/schema.mjs +81 -0
- package/lib/task-graph/store.mjs +71 -0
- package/lib/telemetry/backends/local.mjs +62 -0
- package/lib/telemetry/backends/{langfuse.mjs → remote.mjs} +27 -14
- package/lib/telemetry/backfill.mjs +180 -0
- package/lib/telemetry/eval-datasets.mjs +203 -0
- package/lib/telemetry/{langfuse-ingest.mjs → ingest.mjs} +26 -20
- package/lib/telemetry/intent-verifications.mjs +86 -0
- package/lib/telemetry/llm-judge.mjs +350 -0
- package/lib/telemetry/model-pricing-catalog.mjs +557 -0
- package/lib/telemetry/setup.mjs +151 -0
- package/lib/telemetry/skill-calls.mjs +78 -0
- package/lib/telemetry/team-rollup.mjs +4 -4
- package/lib/token-engine.js +117 -0
- package/lib/token-estimator-anthropic.js +15 -0
- package/lib/token-estimator-deepseek.js +13 -0
- package/lib/token-estimator-default.js +13 -0
- package/lib/token-estimator-google.js +13 -0
- package/lib/token-estimator-openai.js +13 -0
- package/lib/toolkit-env.mjs +1 -1
- package/lib/tty-prompts.mjs +211 -0
- package/lib/uninstall/uninstall.mjs +423 -0
- package/lib/update.mjs +115 -0
- package/lib/upgrade.mjs +141 -0
- package/lib/validator.mjs +51 -2
- package/lib/validators/skills.mjs +142 -0
- package/lib/wireframe.mjs +422 -0
- package/lib/worker/entrypoint.mjs +241 -0
- package/lib/worker/evidence.mjs +107 -0
- package/lib/worker/run.mjs +154 -0
- package/lib/worker/trace.mjs +182 -0
- package/lib/workflow-state.mjs +14 -18
- package/package.json +21 -8
- package/personas/construct.md +53 -51
- package/platforms/claude/CLAUDE.md +1 -1
- package/platforms/claude/settings.template.json +115 -68
- package/platforms/opencode/config.template.json +3 -7
- package/rules/common/agents.md +11 -38
- package/rules/common/beads-hygiene.md +75 -0
- package/rules/common/code-review.md +11 -100
- package/rules/common/coding-style.md +5 -3
- package/rules/common/comments.md +30 -111
- package/rules/common/commit-approval.md +53 -0
- package/rules/common/cx-agent-routing.md +1 -0
- package/rules/common/development-workflow.md +23 -41
- package/rules/common/doc-ownership.md +54 -0
- package/rules/common/efficiency.md +57 -0
- package/rules/common/framing.md +76 -0
- package/rules/common/git-workflow.md +2 -4
- package/rules/common/patterns.md +3 -7
- package/rules/common/performance.md +15 -31
- package/rules/common/release-gates.md +69 -0
- package/rules/common/research.md +107 -0
- package/rules/common/security.md +6 -6
- package/rules/common/skill-composition.md +67 -0
- package/rules/common/testing.md +15 -27
- package/rules/golang/hooks.md +0 -4
- package/rules/policy/bootstrap.yaml +11 -0
- package/rules/policy/drive.yaml +8 -0
- package/rules/policy/task.yaml +9 -0
- package/rules/policy/workflow.yaml +9 -0
- package/rules/python/hooks.md +0 -4
- package/rules/swift/hooks.md +0 -4
- package/rules/typescript/hooks.md +0 -4
- package/rules/web/hooks.md +0 -2
- package/{sync-agents.mjs → scripts/sync-agents.mjs} +306 -61
- package/skills/ai/prompt-optimizer.md +7 -7
- package/skills/compliance/ai-disclosure.md +58 -0
- package/skills/compliance/data-privacy.md +46 -0
- package/skills/compliance/license-audit.md +40 -0
- package/skills/compliance/regulatory-review.md +61 -0
- package/skills/docs/document-ingest-workflow.md +52 -0
- package/skills/docs/evidence-ingest-workflow.md +9 -0
- package/skills/docs/init-docs.md +51 -18
- package/skills/docs/product-intelligence-workflow.md +12 -0
- package/skills/docs/product-signal-workflow.md +4 -0
- package/skills/docs/research-workflow.md +23 -8
- package/skills/docs/runbook-workflow.md +1 -1
- package/skills/operating/orchestration-reference.md +151 -0
- package/skills/roles/architect.md +5 -0
- package/skills/roles/engineer.md +32 -0
- package/skills/roles/operator.md +12 -0
- package/skills/roles/reviewer.md +23 -0
- package/skills/routing.md +1 -1
- package/templates/devcontainer/Dockerfile.devcontainer +38 -0
- package/templates/devcontainer/devcontainer.json +31 -0
- package/templates/distribution/bootstrap.ps1 +142 -0
- package/templates/distribution/bootstrap.sh +196 -0
- package/templates/distribution/run.mjs +187 -0
- package/templates/docs/adr.md +44 -8
- package/templates/docs/changelog-entry.md +43 -0
- package/templates/docs/construct_guide.md +149 -0
- package/templates/docs/evidence-brief.md +2 -2
- package/templates/docs/meta-prd.md +140 -19
- package/templates/docs/onboarding.md +57 -0
- package/templates/docs/prd.md +159 -15
- package/templates/docs/research-brief.md +4 -4
- package/templates/homebrew/construct.rb +67 -0
- package/langfuse/docker-compose.yml +0 -82
- package/lib/eval-harness.mjs +0 -59
- package/lib/hooks/bootstrap-guard.mjs +0 -90
- package/lib/hooks/console-warn.mjs +0 -43
- package/lib/hooks/continuation-enforcer.mjs +0 -72
- package/lib/hooks/drive-guard.mjs +0 -89
- package/lib/hooks/mcp-task-scope.mjs +0 -47
- package/lib/hooks/task-completed-guard.mjs +0 -43
- package/lib/hooks/teammate-idle-guard.mjs +0 -54
- package/lib/hooks/workflow-guard.mjs +0 -62
- package/lib/prompt-composer.mjs +0 -196
- package/lib/review.mjs +0 -429
- package/lib/server/static/app.js +0 -841
- package/lib/telemetry/langfuse-model-sync.mjs +0 -270
- package/rules/common/hooks.md +0 -35
- package/rules/zh/README.md +0 -113
- package/rules/zh/agents.md +0 -55
- package/rules/zh/code-review.md +0 -129
- package/rules/zh/coding-style.md +0 -53
- package/rules/zh/development-workflow.md +0 -49
- package/rules/zh/git-workflow.md +0 -29
- package/rules/zh/hooks.md +0 -35
- package/rules/zh/patterns.md +0 -36
- package/rules/zh/performance.md +0 -60
- package/rules/zh/security.md +0 -34
- 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
|
-
|
|
248
|
-
|
|
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
|
}
|