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