@geraldmaron/construct 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +57 -45
- 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 +273 -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 +104 -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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/flow-selection.mjs — specialist/flow selection for
|
|
3
|
+
* orchestration routing policy: which specialists run, in what order,
|
|
4
|
+
* parallel checks, proactive triggers, and the top-level routeRequest/
|
|
5
|
+
* routeRequestVerified/buildConstructToOrchestratorPacket entry points.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from lib/orchestration-policy.mjs (construct-rf26.10). This is
|
|
8
|
+
* the module that composes classification.mjs (what kind of request) and
|
|
9
|
+
* gates.mjs (what must be true first) into a concrete dispatch plan.
|
|
10
|
+
* orchestration-policy.mjs re-exports these unchanged for existing callers.
|
|
11
|
+
*/
|
|
12
|
+
import { resolveContractChain } from '../specialist-contracts.mjs';
|
|
13
|
+
import { verifyRoute } from '../intent-classifier.mjs';
|
|
14
|
+
import { evaluateWatchConditions } from './routing-tables.mjs';
|
|
15
|
+
import { resolveArtifactWorkflowContract } from '../artifact-manifest.mjs';
|
|
16
|
+
import { buildResearchExecutionPolicy } from '../research-execution-policy.mjs';
|
|
17
|
+
import { EXECUTION_TRACKS, INTENT_CLASSES, WORK_CATEGORIES } from './policy-constants.mjs';
|
|
18
|
+
import {
|
|
19
|
+
classifyEngineerFlavor, isExplorerRequest, isProductIntelligenceRequest,
|
|
20
|
+
isDataAnalysisRequest, isDataEngineeringRequest, detectDocAuthoringIntent,
|
|
21
|
+
isLegalComplianceRequest, isBusinessStrategyRequest, isOperationsPlanningRequest,
|
|
22
|
+
isRdLeadRequest, containsAny, detectRiskFlags, classifyWorkCategory,
|
|
23
|
+
classifyIntent, classifyRoleFlavors, classifyProductManagerFlavor,
|
|
24
|
+
isVisualDeliverableRequest, determineExecutionTrack,
|
|
25
|
+
} from './classification.mjs';
|
|
26
|
+
import {
|
|
27
|
+
requiresExternalResearch, requiresFramingChallenge,
|
|
28
|
+
resolveArtifactReviewRequirements, teamRoutingForSpecialists,
|
|
29
|
+
} from './gates.mjs';
|
|
30
|
+
|
|
31
|
+
const SPECIALIST_MAP = {
|
|
32
|
+
implementation: ['cx-engineer'],
|
|
33
|
+
investigation: ['cx-debugger', 'cx-engineer'],
|
|
34
|
+
evaluation: ['cx-reviewer'],
|
|
35
|
+
fix: ['cx-debugger', 'cx-engineer'],
|
|
36
|
+
research: ['cx-researcher'],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const WORKFLOW_SKILL_TO_TYPE = {
|
|
40
|
+
'docs/prd-workflow': 'prd-draft',
|
|
41
|
+
'docs/adr-workflow': 'architecture-review',
|
|
42
|
+
'docs/research-workflow': 'research-synthesis',
|
|
43
|
+
'docs/evidence-ingest-workflow': 'evidence-ingest',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// AI/platform/data engineering are skill bundles on the single cx-engineer
|
|
47
|
+
// specialist, not separate dispatch targets, so specialist selection never
|
|
48
|
+
// substitutes a flavor-specific id here. The engineer flavor is still
|
|
49
|
+
// detected via classifyEngineerFlavor()/classifyRoleFlavors() and drives
|
|
50
|
+
// which skills/roles/*.md overlay loads onto cx-engineer — see
|
|
51
|
+
// lib/roles/flavor-bindings.mjs for that binding.
|
|
52
|
+
function refineEngineeringSpecialists(specialists) {
|
|
53
|
+
return specialists;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function suggestWorkflowType({ intent, request = '', docAuthoring } = {}) {
|
|
57
|
+
const text = String(request).toLowerCase();
|
|
58
|
+
const localWalkthrough = isExplorerRequest(request)
|
|
59
|
+
|| /\b(?:explain|understand|what does|what is)\b[\s\S]{0,60}\b(?:how\b|do\b|does\b|works?\b|flow\b|path\b|layer\b|module\b|implementation\b|code\b|function\b)/i.test(text)
|
|
60
|
+
|| /\bhow\b[\s\S]{0,40}\bworks?\b/i.test(text);
|
|
61
|
+
if (intent === INTENT_CLASSES.research && !localWalkthrough) {
|
|
62
|
+
return 'research-synthesis';
|
|
63
|
+
}
|
|
64
|
+
if (docAuthoring?.docType) {
|
|
65
|
+
const contract = resolveArtifactWorkflowContract(docAuthoring.docType);
|
|
66
|
+
const workflowType = WORKFLOW_SKILL_TO_TYPE[contract?.workflowSkill];
|
|
67
|
+
if (workflowType) return workflowType;
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function selectSpecialists({ request = '', intent, track, riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
|
|
73
|
+
const text = String(request).toLowerCase();
|
|
74
|
+
const productRequest = isProductIntelligenceRequest(text);
|
|
75
|
+
const dataAnalysisRequest = isDataAnalysisRequest(text);
|
|
76
|
+
const dataEngineeringRequest = isDataEngineeringRequest(text);
|
|
77
|
+
if (track === EXECUTION_TRACKS.immediate) return [];
|
|
78
|
+
if (track === EXECUTION_TRACKS.focused) {
|
|
79
|
+
if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) return ['cx-designer'];
|
|
80
|
+
if (productRequest) return ['cx-product-manager'];
|
|
81
|
+
const docAuthoring = detectDocAuthoringIntent(text);
|
|
82
|
+
if (docAuthoring?.owner) return [docAuthoring.owner];
|
|
83
|
+
if (dataEngineeringRequest) return ['cx-engineer'];
|
|
84
|
+
if (dataAnalysisRequest) return ['cx-data-analyst'];
|
|
85
|
+
if (isLegalComplianceRequest(text)) return ['cx-security'];
|
|
86
|
+
if (isBusinessStrategyRequest(text)) return ['cx-product-manager'];
|
|
87
|
+
if (isOperationsPlanningRequest(text)) return ['cx-operations'];
|
|
88
|
+
if (isRdLeadRequest(text)) return ['cx-architect'];
|
|
89
|
+
if (isExplorerRequest(text)) return ['cx-researcher'];
|
|
90
|
+
if (riskFlags.docs) return ['cx-operations'];
|
|
91
|
+
if (riskFlags.security && intent === INTENT_CLASSES.evaluation) return refineEngineeringSpecialists(['cx-security'], request);
|
|
92
|
+
return refineEngineeringSpecialists(SPECIALIST_MAP[intent] || ['cx-engineer'], request);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const specialists = ['cx-architect'];
|
|
96
|
+
if (intent === INTENT_CLASSES.fix || intent === INTENT_CLASSES.investigation) specialists.push('cx-debugger');
|
|
97
|
+
if (intent === INTENT_CLASSES.research) specialists.push('cx-researcher');
|
|
98
|
+
const docAuthoring = detectDocAuthoringIntent(text);
|
|
99
|
+
if (docAuthoring?.owner) specialists.push(docAuthoring.owner);
|
|
100
|
+
if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui) specialists.push('cx-designer');
|
|
101
|
+
if (productRequest) specialists.push('cx-product-manager');
|
|
102
|
+
if (dataAnalysisRequest) specialists.push('cx-data-analyst');
|
|
103
|
+
if (dataEngineeringRequest) specialists.push('cx-engineer');
|
|
104
|
+
else if (riskFlags.docs) specialists.push('cx-operations');
|
|
105
|
+
specialists.push('cx-engineer', 'cx-reviewer', 'cx-qa');
|
|
106
|
+
if (riskFlags.security || riskFlags.dataIntegrity) specialists.push('cx-security');
|
|
107
|
+
return refineEngineeringSpecialists(Array.from(new Set(specialists)), request);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function augmentSpecialists(
|
|
111
|
+
specialists = [],
|
|
112
|
+
{
|
|
113
|
+
request = '',
|
|
114
|
+
docAuthoring = null,
|
|
115
|
+
externalResearch = null,
|
|
116
|
+
framingChallenge = null,
|
|
117
|
+
artifactReview = null,
|
|
118
|
+
triggers = [],
|
|
119
|
+
} = {},
|
|
120
|
+
) {
|
|
121
|
+
let list = [...specialists];
|
|
122
|
+
if (docAuthoring?.owner && !list.includes(docAuthoring.owner)) {
|
|
123
|
+
list = [docAuthoring.owner, ...list];
|
|
124
|
+
}
|
|
125
|
+
if (externalResearch?.required && !list.includes('cx-researcher')) {
|
|
126
|
+
list = ['cx-researcher', ...list];
|
|
127
|
+
}
|
|
128
|
+
if (framingChallenge?.required && !list.includes('cx-reviewer')) {
|
|
129
|
+
list = ['cx-reviewer', ...list];
|
|
130
|
+
}
|
|
131
|
+
for (const reviewer of artifactReview?.requiredReviewers || []) {
|
|
132
|
+
if (!list.includes(reviewer)) list = [...list, reviewer];
|
|
133
|
+
}
|
|
134
|
+
if (isLegalComplianceRequest(request) && !list.includes('cx-security')) {
|
|
135
|
+
list = ['cx-security', ...list];
|
|
136
|
+
}
|
|
137
|
+
if (isRdLeadRequest(request) && !list.includes('cx-architect')) {
|
|
138
|
+
list = ['cx-architect', ...list];
|
|
139
|
+
}
|
|
140
|
+
if (isBusinessStrategyRequest(request) && !list.includes('cx-product-manager')) {
|
|
141
|
+
list = ['cx-product-manager', ...list];
|
|
142
|
+
}
|
|
143
|
+
for (const { specialist } of triggers) {
|
|
144
|
+
if (!list.includes(specialist)) list = [specialist, ...list];
|
|
145
|
+
}
|
|
146
|
+
return Array.from(new Set(list));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function enrichPolicyForProjectQuestion(request, policySpecialists = []) {
|
|
150
|
+
if (!/\b(what is this (project|repo|codebase)|what('s| is) this (project|repo|codebase)|describe this (project|repo|codebase))\b/i.test(String(request))) {
|
|
151
|
+
return policySpecialists;
|
|
152
|
+
}
|
|
153
|
+
const list = [...policySpecialists];
|
|
154
|
+
if (!list.includes('cx-researcher')) list.unshift('cx-researcher');
|
|
155
|
+
return Array.from(new Set(list));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Identify specialists that can run in parallel with implementation.
|
|
160
|
+
* Returns an array of specialist names that should execute concurrently
|
|
161
|
+
* rather than sequentially. This reduces latency for independent checks.
|
|
162
|
+
*
|
|
163
|
+
* Parallel execution is safe when:
|
|
164
|
+
* - The specialist only reads/analyzes (doesn't mutate)
|
|
165
|
+
* - Their output doesn't depend on implementation completing first
|
|
166
|
+
* - They provide early feedback that can shape implementation
|
|
167
|
+
*/
|
|
168
|
+
export function identifyParallelChecks({ request = '', riskFlags = detectRiskFlags(request), workCategory = classifyWorkCategory(request, riskFlags) } = {}) {
|
|
169
|
+
const parallelChecks = [];
|
|
170
|
+
const text = String(request).toLowerCase();
|
|
171
|
+
|
|
172
|
+
// Security review can run parallel to implementation for early threat detection
|
|
173
|
+
if (riskFlags.security || containsAny(text, ['auth', 'permission', 'secret', 'payment', 'pii'])) {
|
|
174
|
+
parallelChecks.push('cx-security');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Accessibility review can run parallel for UI work
|
|
178
|
+
if (workCategory === WORK_CATEGORIES.visual || riskFlags.ui || containsAny(text, ['ui', 'ux', 'interface', 'component'])) {
|
|
179
|
+
parallelChecks.push('cx-designer');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Performance review for data-intensive operations
|
|
183
|
+
if (containsAny(text, ['performance', 'latency', 'throughput', 'scale', 'optimization'])) {
|
|
184
|
+
parallelChecks.push('cx-operations');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Legal compliance for regulated domains — same specialist as the security
|
|
188
|
+
// check above (legal-compliance folded into cx-security), so dedupe.
|
|
189
|
+
if (isLegalComplianceRequest(request) && !parallelChecks.includes('cx-security')) {
|
|
190
|
+
parallelChecks.push('cx-security');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
return parallelChecks;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Structured request signals computed once and fed to both selectSpecialists
|
|
198
|
+
// and the proactive-trigger pass. Centralises every cheap signal so specialists
|
|
199
|
+
// don't each re-derive them from raw text. Returned shape is stable — additive
|
|
200
|
+
// only.
|
|
201
|
+
|
|
202
|
+
export function requestSignals(request = '', context = {}) {
|
|
203
|
+
const text = String(request).toLowerCase();
|
|
204
|
+
const riskFlags = detectRiskFlags(request);
|
|
205
|
+
const intent = classifyIntent(request);
|
|
206
|
+
const workCategory = classifyWorkCategory(request, riskFlags);
|
|
207
|
+
|
|
208
|
+
const ambiguityIndicators = [
|
|
209
|
+
'maybe', 'something like', 'figure out', 'somehow', 'sort of', 'kind of', 'idea', 'rough', 'tbd',
|
|
210
|
+
'we should probably', 'not sure', 'thinking about',
|
|
211
|
+
];
|
|
212
|
+
const namedConstraintIndicators = [
|
|
213
|
+
'must not', 'cannot exceed', 'has to', 'deadline', 'budget', 'within', 'no later than',
|
|
214
|
+
'maximum', 'minimum', 'sla', 'p95', 'p99',
|
|
215
|
+
];
|
|
216
|
+
const blastRadiusWide = [
|
|
217
|
+
'all users', 'every user', 'global', 'org-wide', 'company-wide', 'breaking change',
|
|
218
|
+
'migration', 'mass update', 'backfill', 'destructive',
|
|
219
|
+
];
|
|
220
|
+
const blastRadiusMedium = [
|
|
221
|
+
'feature flag', 'beta cohort', 'experiment', 'rollout', 'shadow',
|
|
222
|
+
];
|
|
223
|
+
|
|
224
|
+
const ambiguityHits = ambiguityIndicators.filter((kw) => text.includes(kw)).length;
|
|
225
|
+
const ambiguityScore = Math.min(1, ambiguityHits / 3);
|
|
226
|
+
|
|
227
|
+
let blastRadius = 'narrow';
|
|
228
|
+
if (containsAny(text, blastRadiusWide)) blastRadius = 'wide';
|
|
229
|
+
else if (containsAny(text, blastRadiusMedium)) blastRadius = 'medium';
|
|
230
|
+
|
|
231
|
+
return {
|
|
232
|
+
intent,
|
|
233
|
+
workCategory,
|
|
234
|
+
riskFlags,
|
|
235
|
+
ambiguityScore,
|
|
236
|
+
hasSuccessMetric: containsAny(text, ['success metric', 'kpi', 'target metric', 'goal metric', 'acceptance criteria']),
|
|
237
|
+
hasNamedConstraints: containsAny(text, namedConstraintIndicators),
|
|
238
|
+
blastRadius,
|
|
239
|
+
authOrPayments: containsAny(text, ['auth', 'authentication', 'authorization', 'payment', 'pii', 'personal data', 'compliance']),
|
|
240
|
+
visualDeliverable: isVisualDeliverableRequest(text),
|
|
241
|
+
namedUsers: context?.namedUsers || [],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Signal-driven proactive triggers. Returns a list of { specialist, reason }
|
|
246
|
+
// pairs for specialists that should engage PRE-DISPATCH based on signals —
|
|
247
|
+
// separate from the keyword-only paths in selectSpecialists. The watch
|
|
248
|
+
// predicates and their specialist owners are declared in
|
|
249
|
+
// specialists/org (watchConditions) and evaluated by
|
|
250
|
+
// orchestration/routing-tables.mjs.
|
|
251
|
+
|
|
252
|
+
export function proactiveTriggers(signals) {
|
|
253
|
+
return evaluateWatchConditions(signals).map(({ specialist, reason }) => ({ specialist, reason }));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Format active flavor overlays into a one-line trace, e.g.
|
|
257
|
+
// "Overlays: architect=platform (matched kubernetes, infra), engineer=ai (matched llm, agent)"
|
|
258
|
+
|
|
259
|
+
export function formatOverlayTrace(roleFlavors = {}, request = '') {
|
|
260
|
+
const text = String(request).toLowerCase();
|
|
261
|
+
const active = Object.entries(roleFlavors).filter(([, flavor]) => flavor);
|
|
262
|
+
if (active.length === 0) return '';
|
|
263
|
+
const FLAVOR_KEYWORDS = {
|
|
264
|
+
architect: ['agent', 'rag', 'retrieval', 'embedding', 'integration', 'webhook', 'warehouse', 'schema', 'sso', 'rbac', 'platform', 'kubernetes', 'infra'],
|
|
265
|
+
productManager: ['platform', 'api', 'enterprise', 'ai product', 'agent', 'growth', 'activation'],
|
|
266
|
+
qa: ['agent', 'prompt', 'api', 'sdk', 'pipeline', 'etl', 'ui', 'ux', 'accessibility'],
|
|
267
|
+
security: ['prompt injection', 'privacy', 'pii', 'dependency', 'cloud', 'iam', 'auth', 'xss', 'csrf'],
|
|
268
|
+
dataAnalyst: ['experiment', 'a/b', 'telemetry', 'metric', 'funnel'],
|
|
269
|
+
dataEngineer: ['vector', 'embedding', 'warehouse', 'pipeline', 'etl', 'pgvector'],
|
|
270
|
+
engineer: ['llm', 'agent', 'rag', 'kubernetes', 'k8s', 'terraform', 'infra', 'docker', 'pipeline', 'etl', 'data model'],
|
|
271
|
+
};
|
|
272
|
+
const parts = active.map(([role, flavor]) => {
|
|
273
|
+
const kws = (FLAVOR_KEYWORDS[role] || []).filter((kw) => text.includes(kw));
|
|
274
|
+
const matched = kws.slice(0, 3).join(', ');
|
|
275
|
+
return matched ? `${role}=${flavor} (matched ${matched})` : `${role}=${flavor}`;
|
|
276
|
+
});
|
|
277
|
+
return `Overlays: ${parts.join(', ')}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function routeRequest(options = {}) {
|
|
281
|
+
const intent = classifyIntent(options.request);
|
|
282
|
+
const riskFlags = detectRiskFlags(options.request);
|
|
283
|
+
const roleFlavors = classifyRoleFlavors(options.request);
|
|
284
|
+
const productFlavor = isProductIntelligenceRequest(options.request)
|
|
285
|
+
? classifyProductManagerFlavor(options.request)
|
|
286
|
+
: null;
|
|
287
|
+
const workCategory = classifyWorkCategory(options.request, riskFlags);
|
|
288
|
+
const track = determineExecutionTrack({ ...options, riskFlags });
|
|
289
|
+
const docAuthoring = detectDocAuthoringIntent(options.request);
|
|
290
|
+
const externalResearch = requiresExternalResearch({ request: options.request, workCategory, riskFlags });
|
|
291
|
+
const framingChallenge = requiresFramingChallenge({ request: options.request, workCategory, riskFlags, introducesContract: options.introducesContract });
|
|
292
|
+
const artifactReview = resolveArtifactReviewRequirements(docAuthoring);
|
|
293
|
+
const suggestedWorkflowType = suggestWorkflowType({ intent, request: options.request, docAuthoring });
|
|
294
|
+
const researchExecutionPolicy = intent === INTENT_CLASSES.research || externalResearch?.required
|
|
295
|
+
? buildResearchExecutionPolicy({ request: options.request })
|
|
296
|
+
: null;
|
|
297
|
+
const signals = requestSignals(options.request, options.context);
|
|
298
|
+
const triggers = proactiveTriggers(signals);
|
|
299
|
+
const reasons = {};
|
|
300
|
+
for (const { specialist, reason } of triggers) {
|
|
301
|
+
reasons[specialist] = reason;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const augmentOpts = {
|
|
305
|
+
request: options.request,
|
|
306
|
+
docAuthoring,
|
|
307
|
+
externalResearch,
|
|
308
|
+
framingChallenge,
|
|
309
|
+
artifactReview,
|
|
310
|
+
triggers,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
let specialists = augmentSpecialists(
|
|
314
|
+
selectSpecialists({ ...options, intent, track, riskFlags, workCategory }),
|
|
315
|
+
augmentOpts,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const policyTrack = track === EXECUTION_TRACKS.immediate ? EXECUTION_TRACKS.focused : track;
|
|
319
|
+
let policySpecialists = augmentSpecialists(
|
|
320
|
+
selectSpecialists({ ...options, intent, track: policyTrack, riskFlags, workCategory }),
|
|
321
|
+
augmentOpts,
|
|
322
|
+
);
|
|
323
|
+
policySpecialists = enrichPolicyForProjectQuestion(options.request, policySpecialists);
|
|
324
|
+
|
|
325
|
+
const displaySpecialists = specialists.length ? specialists : policySpecialists;
|
|
326
|
+
|
|
327
|
+
const contractChain = resolveContractChain({
|
|
328
|
+
intent,
|
|
329
|
+
workCategory,
|
|
330
|
+
track,
|
|
331
|
+
riskFlags,
|
|
332
|
+
specialists: displaySpecialists,
|
|
333
|
+
framingChallenge,
|
|
334
|
+
externalResearch,
|
|
335
|
+
docAuthoring,
|
|
336
|
+
artifactReview,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Team-aware routing: analyze specialist-to-team mapping and identify
|
|
340
|
+
// team involvement, primary owner, required approvals, and escalation paths.
|
|
341
|
+
const teamRouting = teamRoutingForSpecialists(displaySpecialists, {
|
|
342
|
+
intent,
|
|
343
|
+
request: options.request,
|
|
344
|
+
cwd: options.cwd ?? null,
|
|
345
|
+
docAuthoring,
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const route = {
|
|
349
|
+
intent,
|
|
350
|
+
workCategory,
|
|
351
|
+
track,
|
|
352
|
+
riskFlags,
|
|
353
|
+
productFlavor,
|
|
354
|
+
roleFlavors,
|
|
355
|
+
specialists,
|
|
356
|
+
policySpecialists,
|
|
357
|
+
displaySpecialists,
|
|
358
|
+
signals,
|
|
359
|
+
triggers,
|
|
360
|
+
dispatchReasons: reasons,
|
|
361
|
+
docAuthoring,
|
|
362
|
+
externalResearch,
|
|
363
|
+
framingChallenge,
|
|
364
|
+
artifactReview,
|
|
365
|
+
suggestedWorkflowType,
|
|
366
|
+
researchExecutionPolicy,
|
|
367
|
+
contractChain,
|
|
368
|
+
teamRouting,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
if (process.env.CONSTRUCT_VERBOSE === '1' && triggers.length > 0) {
|
|
372
|
+
for (const { specialist, reason } of triggers) {
|
|
373
|
+
console.error(`[route] proactive: ${specialist} (${reason})`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return route;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Sync wrapper that returns the keyword route immediately and fires the
|
|
381
|
+
// LLM intent verifier in the background. The verifier writes its verdict
|
|
382
|
+
// to ~/.cx/intent-verifications.jsonl for offline tuning; the dispatched
|
|
383
|
+
// route never waits on a model round-trip. Disable the background call
|
|
384
|
+
// entirely with CONSTRUCT_INTENT_VERIFY=off — see lib/intent-classifier.mjs.
|
|
385
|
+
export function routeRequestVerified(options = {}) {
|
|
386
|
+
const route = routeRequest(options);
|
|
387
|
+
return verifyRoute(route, { request: options.request, modelCaller: options.modelCaller });
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Build the construct→orchestrator contract packet (specialists/org).
|
|
392
|
+
* Callers must pass this object to agent_contract — not a bare goal string.
|
|
393
|
+
*/
|
|
394
|
+
export function buildConstructToOrchestratorPacket(options = {}) {
|
|
395
|
+
const request = String(options.request || '');
|
|
396
|
+
const route = options.route || routeRequest({
|
|
397
|
+
request,
|
|
398
|
+
fileCount: options.fileCount ?? 0,
|
|
399
|
+
moduleCount: options.moduleCount ?? 0,
|
|
400
|
+
introducesContract: options.introducesContract ?? false,
|
|
401
|
+
explicitDrive: options.explicitDrive ?? false,
|
|
402
|
+
});
|
|
403
|
+
if (route.track === EXECUTION_TRACKS.immediate) return null;
|
|
404
|
+
|
|
405
|
+
const goal = String(options.goal || request || '').trim() || 'User-requested work';
|
|
406
|
+
const acceptanceCriteria = Array.isArray(options.acceptanceCriteria) && options.acceptanceCriteria.length
|
|
407
|
+
? options.acceptanceCriteria
|
|
408
|
+
: ['Dispatch plan emitted with specialists in sequence', 'Acceptance criteria verified before close'];
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
goal,
|
|
412
|
+
intent: route.intent,
|
|
413
|
+
workCategory: route.workCategory,
|
|
414
|
+
riskFlags: route.riskFlags || {},
|
|
415
|
+
suggestedWorkflowType: route.suggestedWorkflowType || null,
|
|
416
|
+
acceptanceCriteria,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/gates.mjs — gate evaluation for orchestration routing
|
|
3
|
+
* policy: research/framing/executive-approval gates, artifact review
|
|
4
|
+
* requirements, and team-aware routing (blocked-decision escalation).
|
|
5
|
+
*
|
|
6
|
+
* Extracted from lib/orchestration-policy.mjs (construct-rf26.10). Every
|
|
7
|
+
* function here answers "must this be true before work starts / who must
|
|
8
|
+
* approve" — as distinct from classification.mjs (what kind of request is
|
|
9
|
+
* this) and flow-selection.mjs (which specialists/chain run it).
|
|
10
|
+
* orchestration-policy.mjs re-exports these unchanged for existing callers.
|
|
11
|
+
*/
|
|
12
|
+
import { getArtifactEntry } from '../artifact-manifest.mjs';
|
|
13
|
+
import { loadRegistry } from '../registry/loader.mjs';
|
|
14
|
+
import { resolveActiveScope } from '../scopes/loader.mjs';
|
|
15
|
+
import { scopeTeamsById, resolveIntentTeamForScope, resolveScopeTeamMeta } from '../scopes/teams.mjs';
|
|
16
|
+
import { WORK_CATEGORIES, INTENT_TO_TEAM } from './policy-constants.mjs';
|
|
17
|
+
import {
|
|
18
|
+
classifyWorkCategory, detectRiskFlags, requiresLiveWebAccess,
|
|
19
|
+
extractNamedEntities, classifyResearchShape, isProductIntelligenceRequest,
|
|
20
|
+
detectDocAuthoringIntent,
|
|
21
|
+
} from './classification.mjs';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Returns whether external research is required before scaffolding, with the
|
|
25
|
+
* reason. Triggered by named entities not in the project glossary, by
|
|
26
|
+
* architecture / writing / docs work, or by research-shaped intent regardless of
|
|
27
|
+
* entities.
|
|
28
|
+
*/
|
|
29
|
+
export function requiresExternalResearch({ request = '', workCategory, riskFlags } = {}) {
|
|
30
|
+
const entities = extractNamedEntities(request);
|
|
31
|
+
const category = workCategory ?? classifyWorkCategory(request);
|
|
32
|
+
const flags = riskFlags ?? detectRiskFlags(request);
|
|
33
|
+
if (requiresLiveWebAccess(request)) {
|
|
34
|
+
return { required: true, reason: 'web-access' };
|
|
35
|
+
}
|
|
36
|
+
if (entities.length > 0) {
|
|
37
|
+
return { required: true, reason: 'named-entities', entities };
|
|
38
|
+
}
|
|
39
|
+
if (category === WORK_CATEGORIES.writing || flags.architecture || flags.docs) {
|
|
40
|
+
return { required: true, reason: 'writing-or-architecture' };
|
|
41
|
+
}
|
|
42
|
+
// Research-shaped intent (compare, landscape, market, standards, …) fires even
|
|
43
|
+
// without a named entity, since the answer is external. Bare research intent
|
|
44
|
+
// does not: a code walkthrough ("explain how X works", "understand the
|
|
45
|
+
// retrieval path") carries none of these terms and stays answered from local
|
|
46
|
+
// context — the distinction that keeps the gate from firing on every prompt.
|
|
47
|
+
const shape = classifyResearchShape(request);
|
|
48
|
+
if (shape) {
|
|
49
|
+
return { required: true, reason: 'research-shaped', shape };
|
|
50
|
+
}
|
|
51
|
+
return { required: false };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns whether the request must pass a framing challenge
|
|
56
|
+
* (cx-reviewer's plan-challenge mode or cx-architect problem-reframing) before scaffolding.
|
|
57
|
+
* Fires for architecture work, documentation sets, and any research-driven
|
|
58
|
+
* artifact. Fail-closed: when in doubt, require the challenge.
|
|
59
|
+
*/
|
|
60
|
+
export function requiresFramingChallenge({ request = '', workCategory, riskFlags, introducesContract = false } = {}) {
|
|
61
|
+
const category = workCategory ?? classifyWorkCategory(request);
|
|
62
|
+
const flags = riskFlags ?? detectRiskFlags(request);
|
|
63
|
+
if (flags.architecture || introducesContract) return { required: true, reason: 'architecture-or-contract' };
|
|
64
|
+
if (category === WORK_CATEGORIES.writing && flags.docs) return { required: true, reason: 'documentation-set' };
|
|
65
|
+
if (isProductIntelligenceRequest(request)) return { required: true, reason: 'product-intelligence' };
|
|
66
|
+
if (detectDocAuthoringIntent(request)) return { required: true, reason: 'typed-document' };
|
|
67
|
+
return { required: false };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function requiresExecutiveApproval({
|
|
71
|
+
scopeChange = false,
|
|
72
|
+
productDecision = false,
|
|
73
|
+
riskAcceptance = false,
|
|
74
|
+
irreversibleAction = false,
|
|
75
|
+
blockedDependency = false,
|
|
76
|
+
} = {}) {
|
|
77
|
+
return Boolean(scopeChange || productDecision || riskAcceptance || irreversibleAction || blockedDependency);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function resolveArtifactReviewRequirements(docAuthoring) {
|
|
81
|
+
if (!docAuthoring?.docType) {
|
|
82
|
+
return { requiredReviewers: [], optionalReviewers: [], releaseGate: null };
|
|
83
|
+
}
|
|
84
|
+
const entry = getArtifactEntry(docAuthoring.docType);
|
|
85
|
+
const gate = entry?.releaseGate ?? {};
|
|
86
|
+
return {
|
|
87
|
+
requiredReviewers: gate.requiredReviewers ?? [],
|
|
88
|
+
optionalReviewers: gate.optionalReviewers ?? [],
|
|
89
|
+
releaseGate: gate,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Map a free-text request to the decision names that gate it. Keyed against the
|
|
94
|
+
// union of every team's forbiddenDecisions so a BLOCKED status fires only when
|
|
95
|
+
// the request actually asks for a decision the primary team cannot make. Ordered
|
|
96
|
+
// most-specific-first (deployment-timing before deployment) so the narrow match
|
|
97
|
+
// wins.
|
|
98
|
+
|
|
99
|
+
const REQUESTED_DECISION_PATTERNS = [
|
|
100
|
+
[/\bsecurity[ -]?override\b|\boverride\s+(the\s+)?security\b/i, 'security-override'],
|
|
101
|
+
[/\bdeployment[ -]?timing\b|\bwhen\s+to\s+deploy\b|\bdeploy\s+(?:immediately|now)\b/i, 'deployment-timing'],
|
|
102
|
+
[/\bdeployment[ -]?readiness\b/i, 'deployment-readiness'],
|
|
103
|
+
[/\bdeploy(?:ment|ing|s)?\b|\brelease\s+to\s+prod/i, 'deployment'],
|
|
104
|
+
[/\binfra(?:structure)?[ -]?change\b|\bchange\s+(?:the\s+)?infra/i, 'infra-change'],
|
|
105
|
+
[/\bscope[ -]?change\b|\bchange\s+(?:the\s+)?scope\b|\bre-?scope\b/i, 'scope-change'],
|
|
106
|
+
[/\bproduct[ -]?scope\b/i, 'product-scope'],
|
|
107
|
+
[/\buser[ -]?research\s+method/i, 'user-research-methods'],
|
|
108
|
+
[/\buser[ -]?research\b/i, 'user-research'],
|
|
109
|
+
[/\bimplementation[ -]?approach\b/i, 'implementation-approach'],
|
|
110
|
+
[/\bimplementation[ -]?detail/i, 'implementation-details'],
|
|
111
|
+
[/\bsecurity[ -]?policy\b/i, 'security-policy'],
|
|
112
|
+
[/\bops[ -]?procedure|operational\s+procedure/i, 'ops-procedures'],
|
|
113
|
+
[/\barchitect/i, 'architecture'],
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
function detectRequestedDecisions(request = '') {
|
|
117
|
+
const text = String(request || '');
|
|
118
|
+
const found = new Set();
|
|
119
|
+
for (const [pattern, decision] of REQUESTED_DECISION_PATTERNS) {
|
|
120
|
+
if (pattern.test(text)) found.add(decision);
|
|
121
|
+
}
|
|
122
|
+
return Array.from(found);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Build team-aware routing metadata (RFC-0004 §2).
|
|
127
|
+
* Returns { primaryTeam, involvedTeams, requiredApprovals, escalationPath, blockedStatus }.
|
|
128
|
+
*
|
|
129
|
+
* The primary team is intent-driven (INTENT_TO_TEAM), falling back to the first
|
|
130
|
+
* specialist's team when the intent has no mapping. involvedTeams unions the
|
|
131
|
+
* primary team with every selected specialist's team. blockedStatus fires when
|
|
132
|
+
* the primary team's forbiddenDecisions intersect the decisions the request asks
|
|
133
|
+
* for; escalation walks the primary team's path first. Team membership and team
|
|
134
|
+
* metadata resolve from specialists/org.
|
|
135
|
+
*/
|
|
136
|
+
export function teamRoutingForSpecialists(specialists = [], { intent = null, request = '', cwd = null, docAuthoring = null } = {}) {
|
|
137
|
+
let registry = null;
|
|
138
|
+
try {
|
|
139
|
+
registry = loadRegistry();
|
|
140
|
+
} catch {
|
|
141
|
+
return {
|
|
142
|
+
primaryTeam: null,
|
|
143
|
+
involvedTeams: [],
|
|
144
|
+
requiredApprovals: [],
|
|
145
|
+
escalationPath: [],
|
|
146
|
+
blockedStatus: null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const profile = cwd ? resolveActiveScope(cwd) : null;
|
|
151
|
+
const profileTeams = scopeTeamsById(profile);
|
|
152
|
+
|
|
153
|
+
const teams = new Map();
|
|
154
|
+
const involvedTeamIds = new Set();
|
|
155
|
+
const addTeam = (teamId) => {
|
|
156
|
+
if (!teamId) return;
|
|
157
|
+
involvedTeamIds.add(teamId);
|
|
158
|
+
if (teams.has(teamId)) return;
|
|
159
|
+
const profileTeam = profileTeams ? resolveScopeTeamMeta(teamId, profile) : null;
|
|
160
|
+
teams.set(teamId, profileTeam || registry.teams?.[teamId] || null);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
for (const specialistId of specialists) {
|
|
164
|
+
const cxId = specialistId.startsWith('cx-') ? specialistId : `cx-${specialistId}`;
|
|
165
|
+
const specialist = registry.specialists?.[cxId];
|
|
166
|
+
if (specialist?.team) addTeam(specialist.team);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// RFC-0004 §2 step 2: intent selects the primary owning team. It always counts
|
|
170
|
+
// as involved even if no selected specialist sits on it yet.
|
|
171
|
+
const profileIntentTeam = resolveIntentTeamForScope(intent, profile);
|
|
172
|
+
const intentTeam = profileIntentTeam
|
|
173
|
+
?? (intent && INTENT_TO_TEAM[intent] ? INTENT_TO_TEAM[intent] : null);
|
|
174
|
+
addTeam(intentTeam);
|
|
175
|
+
|
|
176
|
+
const involvedTeams = Array.from(involvedTeamIds).sort();
|
|
177
|
+
let primaryTeam = intentTeam || (involvedTeams.length > 0 ? involvedTeams[0] : null);
|
|
178
|
+
|
|
179
|
+
if (docAuthoring?.owner) {
|
|
180
|
+
const cxId = docAuthoring.owner.startsWith('cx-') ? docAuthoring.owner : `cx-${docAuthoring.owner}`;
|
|
181
|
+
const ownerTeam = registry.specialists?.[cxId]?.team;
|
|
182
|
+
if (ownerTeam) {
|
|
183
|
+
addTeam(ownerTeam);
|
|
184
|
+
primaryTeam = ownerTeam;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Collect required approvals from involved teams' decision rights.
|
|
189
|
+
const requiredApprovals = new Set();
|
|
190
|
+
for (const teamId of involvedTeamIds) {
|
|
191
|
+
const team = teams.get(teamId);
|
|
192
|
+
if (team?.decisionRights) {
|
|
193
|
+
for (const decision of team.decisionRights) requiredApprovals.add(decision);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// RFC-0004 §2 step 5: BLOCKED when the primary team forbids a decision the
|
|
198
|
+
// request actually asks for. Escalate via that team's path.
|
|
199
|
+
let blockedStatus = null;
|
|
200
|
+
const primaryTeamObj = primaryTeam ? (teams.get(primaryTeam) || registry.teams?.[primaryTeam]) : null;
|
|
201
|
+
if (Array.isArray(primaryTeamObj?.forbiddenDecisions)) {
|
|
202
|
+
const blocked = detectRequestedDecisions(request)
|
|
203
|
+
.filter((decision) => primaryTeamObj.forbiddenDecisions.includes(decision));
|
|
204
|
+
if (blocked.length > 0) {
|
|
205
|
+
blockedStatus = {
|
|
206
|
+
team: primaryTeam,
|
|
207
|
+
forbiddenDecisions: blocked,
|
|
208
|
+
escalationPath: primaryTeamObj.escalationPath || [],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Concatenate escalation paths, primary team first, de-duplicated.
|
|
214
|
+
const escalationPath = [];
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
const orderedTeams = primaryTeam
|
|
217
|
+
? [primaryTeam, ...involvedTeams.filter((t) => t !== primaryTeam)]
|
|
218
|
+
: involvedTeams;
|
|
219
|
+
for (const teamId of orderedTeams) {
|
|
220
|
+
const team = teams.get(teamId) || registry.teams?.[teamId];
|
|
221
|
+
if (Array.isArray(team?.escalationPath)) {
|
|
222
|
+
for (const role of team.escalationPath) {
|
|
223
|
+
if (!seen.has(role)) {
|
|
224
|
+
escalationPath.push(role);
|
|
225
|
+
seen.add(role);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const squadId = primaryTeamObj?.kind === 'squad' ? primaryTeam : null;
|
|
232
|
+
const groupId = primaryTeamObj?.kind === 'group' ? primaryTeam : (primaryTeamObj?.groupId ?? null);
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
primaryTeam,
|
|
236
|
+
squadId,
|
|
237
|
+
groupId,
|
|
238
|
+
collaborators: primaryTeamObj?.collaborators || [],
|
|
239
|
+
involvedTeams,
|
|
240
|
+
requiredApprovals: Array.from(requiredApprovals).sort(),
|
|
241
|
+
escalationPath,
|
|
242
|
+
blockedStatus,
|
|
243
|
+
};
|
|
244
|
+
}
|