@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,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/classification.mjs — text classifiers for orchestration
|
|
3
|
+
* routing policy: intent, work category, risk flags, execution track, role
|
|
4
|
+
* flavor overlays, doc-authoring detection, named-entity/research-shape/live-
|
|
5
|
+
* web-access signals.
|
|
6
|
+
*
|
|
7
|
+
* Extracted from lib/orchestration-policy.mjs (construct-rf26.10). Every
|
|
8
|
+
* function here is a pure classifier over request text (or over another
|
|
9
|
+
* classifier's output) — no gate evaluation (requires*) and no
|
|
10
|
+
* specialist/flow selection lives in this module. orchestration-policy.mjs
|
|
11
|
+
* re-exports these unchanged for existing callers.
|
|
12
|
+
*/
|
|
13
|
+
import { ownerForDoc } from './routing-tables.mjs';
|
|
14
|
+
import { formatOverlayLine } from '../roles/flavor-bindings.mjs';
|
|
15
|
+
import { EXECUTION_TRACKS, WORK_CATEGORIES, INTENT_CLASSES } from './policy-constants.mjs';
|
|
16
|
+
|
|
17
|
+
export function includesAny(text, patterns) {
|
|
18
|
+
return patterns.some((pattern) => pattern.test(text));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function containsAny(text, words) {
|
|
22
|
+
return words.some((word) => text.includes(word));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const DOC_AUTHORING_PATTERNS = [
|
|
26
|
+
{ pattern: /\b(adr|architecture decision record)s?\b/i, docType: 'adr' },
|
|
27
|
+
{ pattern: /\bmeta[\s-]prd\b/i, docType: 'meta-prd' },
|
|
28
|
+
{ pattern: /\bplatform prd\b/i, docType: 'prd-platform' },
|
|
29
|
+
{ pattern: /\bbusiness prd\b/i, docType: 'prd-business' },
|
|
30
|
+
{ pattern: /\bprd\b|\bproduct requirements? document/i, docType: 'prd' },
|
|
31
|
+
{ pattern: /\bprfaq\b|\bpress release.*faq/i, docType: 'prfaq' },
|
|
32
|
+
{ pattern: /\bone[\s-]pager\b/i, docType: 'one-pager' },
|
|
33
|
+
{ pattern: /\bbacklog proposal/i, docType: 'backlog-proposal' },
|
|
34
|
+
{ pattern: /\bcustomer profile/i, docType: 'customer-profile' },
|
|
35
|
+
{ pattern: /\brfc\b|\brequest for comments?\b/i, docType: 'rfc' },
|
|
36
|
+
{ pattern: /\barchitecture overview/i, docType: 'architecture-overview' },
|
|
37
|
+
{ pattern: /\bsystem design/i, docType: 'system-design' },
|
|
38
|
+
{ pattern: /\bresearch brief/i, docType: 'research-brief' },
|
|
39
|
+
{ pattern: /\bevidence brief/i, docType: 'evidence-brief' },
|
|
40
|
+
{ pattern: /\bsignal brief/i, docType: 'signal-brief' },
|
|
41
|
+
{ pattern: /\bproduct intelligence report/i, docType: 'product-intelligence-report' },
|
|
42
|
+
{ pattern: /\brunbook/i, docType: 'runbook' },
|
|
43
|
+
{ pattern: /\bincident report/i, docType: 'incident-report' },
|
|
44
|
+
{ pattern: /\bpostmortem|\bpost[\s-]mortem/i, docType: 'postmortem' },
|
|
45
|
+
{ pattern: /\btest plan/i, docType: 'test-plan' },
|
|
46
|
+
{ pattern: /\bqa strategy/i, docType: 'qa-strategy' },
|
|
47
|
+
{ pattern: /\bthreat model/i, docType: 'threat-model' },
|
|
48
|
+
{ pattern: /\bsecurity review/i, docType: 'security-review' },
|
|
49
|
+
{ pattern: /\bchangelog/i, docType: 'changelog' },
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
const AUTHORING_VERBS = /\b(write|writing|draft|drafting|create|creating|author|authoring|produce|producing|compose|composing|prepare|preparing|generate|generating)\b/i;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Resolves a document type mentioned in text without requiring an authoring verb.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveDocTypeMention(request = '') {
|
|
58
|
+
const text = String(request);
|
|
59
|
+
for (const { pattern, docType } of DOC_AUTHORING_PATTERNS) {
|
|
60
|
+
if (pattern.test(text)) return docType;
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Detects whether the request is asking for authorship of a typed document
|
|
67
|
+
* that has a canonical owner. Returns `{ docType, owner }` when matched,
|
|
68
|
+
* or null otherwise.
|
|
69
|
+
*/
|
|
70
|
+
export function detectDocAuthoringIntent(request = '') {
|
|
71
|
+
const text = String(request);
|
|
72
|
+
if (!AUTHORING_VERBS.test(text)) return null;
|
|
73
|
+
for (const { pattern, docType } of DOC_AUTHORING_PATTERNS) {
|
|
74
|
+
if (pattern.test(text)) {
|
|
75
|
+
return { docType, owner: ownerForDoc(docType) };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const PROPER_NOUN_STOPLIST = new Set([
|
|
82
|
+
'I', 'A', 'An', 'The', 'This', 'That', 'These', 'Those', 'My', 'Our', 'Your',
|
|
83
|
+
'We', 'They', 'He', 'She', 'It', 'Please', 'Thanks', 'Hi', 'Hello',
|
|
84
|
+
'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday',
|
|
85
|
+
'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August',
|
|
86
|
+
'September', 'October', 'November', 'December',
|
|
87
|
+
'Construct', 'Claude', 'GPT', 'OpenAI', 'Anthropic', 'GitHub', 'Google', 'Microsoft',
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Extracts proper-noun candidates from a request — capitalized tokens that are
|
|
92
|
+
* not common English words, day/month names, or known product/company names.
|
|
93
|
+
* Detects named entities that likely require external research.
|
|
94
|
+
*/
|
|
95
|
+
export function extractNamedEntities(request = '') {
|
|
96
|
+
const text = String(request);
|
|
97
|
+
const tokens = new Set();
|
|
98
|
+
// CamelCase single words (e.g. "ProjectIverson")
|
|
99
|
+
for (const match of text.matchAll(/\b[A-Z][a-z]+[A-Z][A-Za-z]+\b/g)) tokens.add(match[0]);
|
|
100
|
+
// Capitalized multi-word sequences (e.g. "Project Iverson")
|
|
101
|
+
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]);
|
|
102
|
+
// Single capitalized words not at sentence start
|
|
103
|
+
for (const match of text.matchAll(/(?<=[a-z,;:]\s)[A-Z][a-z]{3,}\b/g)) tokens.add(match[0]);
|
|
104
|
+
return Array.from(tokens).filter((token) => {
|
|
105
|
+
const head = token.split(/\s+/)[0];
|
|
106
|
+
return !PROPER_NOUN_STOPLIST.has(head);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Research-shaped intent is the vocabulary of external and landscape research —
|
|
111
|
+
// where the answer lives in papers, vendor docs, standards, or the market, not
|
|
112
|
+
// in the local code. Grouped by shape so the matched kind rides on the gate
|
|
113
|
+
// result and the decision is traceable. A false positive here is cheap (it only
|
|
114
|
+
// offers the cx-researcher specialist, with no routing latency and no heavy-flow
|
|
115
|
+
// coupling), so the set is tuned for recall; the precision floor is that a
|
|
116
|
+
// code-walkthrough of the user's own system ("explain how X works") carries none
|
|
117
|
+
// of these terms and stays answered from local context.
|
|
118
|
+
const RESEARCH_SHAPE_PATTERNS = [
|
|
119
|
+
['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/]],
|
|
120
|
+
['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/]],
|
|
121
|
+
['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/]],
|
|
122
|
+
['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/]],
|
|
123
|
+
['benchmark', [/\bbenchmarks?\b/, /\bbenchmarking\b/, /\bevaluate (options|alternatives|tools|approaches)\b/, /\bperformance comparison\b/]],
|
|
124
|
+
['standards', [/\bbest practices?\b/, /\bindustry standard\b/, /\bconventions? for\b/, /\brecommended (way|approach|practice)\b/, /\brfc\s?\d+\b/, /\bspecification for\b/]],
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Classify a request's research shape, or null when it carries none. The
|
|
129
|
+
* returned category names which kind of external research the prompt implies.
|
|
130
|
+
*/
|
|
131
|
+
export function classifyResearchShape(request = '') {
|
|
132
|
+
const text = String(request).toLowerCase();
|
|
133
|
+
for (const [category, patterns] of RESEARCH_SHAPE_PATTERNS) {
|
|
134
|
+
if (includesAny(text, patterns)) return category;
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Live web access is distinct from research-shape: a bare "connect to the
|
|
140
|
+
// internet" or "fetch example.com" carries none of the comparative/landscape
|
|
141
|
+
// vocabulary, so without this it falls to the immediate track and the
|
|
142
|
+
// orchestrator — which holds no network tools — refuses instead of dispatching
|
|
143
|
+
// the web-capable cx-researcher. A literal URL, an explicit online/internet
|
|
144
|
+
// phrase, or a fetch/scrape verb paired with a web object is the signal.
|
|
145
|
+
|
|
146
|
+
const WEB_VERB = '(?:fetch|download|open|load|read|scrape|crawl|retrieve|pull|grab|access|visit|hit|reach)';
|
|
147
|
+
const WEB_OBJECT = '(?:url|link|web\\s?page|web\\s?site|site|page|online|internet|web|endpoint|api)';
|
|
148
|
+
const LIVE_WEB_ACCESS_PATTERNS = [
|
|
149
|
+
/https?:\/\/\S+/i,
|
|
150
|
+
/\bwww\.[a-z0-9-]+\.[a-z]{2,}/i,
|
|
151
|
+
/\b(?:connect|connecting|connection)\s+to\s+the\s+(?:internet|web|network)\b/i,
|
|
152
|
+
/\binternet\s+(?:access|connection|connectivity)\b/i,
|
|
153
|
+
/\b(?:go|get|getting|going)\s+online\b/i,
|
|
154
|
+
/\b(?:browse|browsing|surf)\b[\s\S]{0,20}\b(?:web|internet|site|page|url)\b/i,
|
|
155
|
+
/\b(?:curl|wget|ping)\b/i,
|
|
156
|
+
/\b(?:is|are)\b[\s\S]{0,40}\b(?:online|offline|reachable|unreachable|responding)\b/i,
|
|
157
|
+
new RegExp(`\\b${WEB_VERB}\\b[\\s\\S]{0,40}\\b${WEB_OBJECT}\\b`, 'i'),
|
|
158
|
+
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'),
|
|
159
|
+
];
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Whether a request needs live external web/network access the orchestrator
|
|
163
|
+
* cannot perform itself (it is a read-only router with no network tools), so it
|
|
164
|
+
* must be routed to cx-researcher rather than answered immediately.
|
|
165
|
+
*/
|
|
166
|
+
export function requiresLiveWebAccess(request = '') {
|
|
167
|
+
const text = String(request);
|
|
168
|
+
return LIVE_WEB_ACCESS_PATTERNS.some((pattern) => pattern.test(text));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function isProductIntelligenceRequest(request = '') {
|
|
172
|
+
const text = String(request).toLowerCase();
|
|
173
|
+
return containsAny(text, [
|
|
174
|
+
'requirements',
|
|
175
|
+
'prd',
|
|
176
|
+
'prfaq',
|
|
177
|
+
'product brief',
|
|
178
|
+
'signal brief',
|
|
179
|
+
'customer notes',
|
|
180
|
+
'customer profile',
|
|
181
|
+
'product intelligence',
|
|
182
|
+
'backlog proposal',
|
|
183
|
+
'jira proposal',
|
|
184
|
+
'linear proposal',
|
|
185
|
+
'field notes',
|
|
186
|
+
'product spec',
|
|
187
|
+
'meta prd',
|
|
188
|
+
]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export function classifyProductManagerFlavor(request = '') {
|
|
192
|
+
const text = String(request).toLowerCase();
|
|
193
|
+
if (containsAny(text, ['platform', 'api', 'sdk', 'developer experience', 'devex', 'integration', 'migration', 'compatibility', 'admin', 'tenant'])) return 'platform';
|
|
194
|
+
if (containsAny(text, ['enterprise', 'procurement', 'compliance', 'security review', 'audit', 'sso', 'soc2', 'soc 2', 'rbac'])) return 'enterprise';
|
|
195
|
+
if (containsAny(text, ['ai product', 'agent', 'eval', 'evaluation loop', 'model behavior', 'prompt', 'llm', 'human review'])) return 'ai-product';
|
|
196
|
+
if (containsAny(text, ['growth', 'activation', 'conversion', 'funnel', 'packaging', 'pricing', 'gtm', 'go-to-market'])) return 'growth';
|
|
197
|
+
return 'product';
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function classifyArchitectFlavor(request = '') {
|
|
201
|
+
const text = String(request).toLowerCase();
|
|
202
|
+
if (containsAny(text, ['agent', 'rag', 'retrieval', 'embedding', 'eval loop', 'model behavior', 'tool use', 'llm'])) return 'ai-systems';
|
|
203
|
+
if (containsAny(text, ['integration', 'webhook', 'sync', 'third-party', 'oauth', 'reconciliation', 'idempotency'])) return 'integration';
|
|
204
|
+
if (containsAny(text, ['warehouse', 'schema', 'migration', 'retention', 'index', 'backfill', 'data model'])) return 'data';
|
|
205
|
+
if (containsAny(text, ['enterprise', 'sso', 'rbac', 'audit', 'data residency', 'procurement', 'tenant isolation'])) return 'enterprise';
|
|
206
|
+
if (containsAny(text, ['platform', 'api', 'sdk', 'developer experience', 'devex', 'admin', 'tenant', 'compatibility'])) return 'platform';
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function classifyQaFlavor(request = '') {
|
|
211
|
+
const text = String(request).toLowerCase();
|
|
212
|
+
if (containsAny(text, ['agent', 'prompt', 'model', 'eval', 'rag', 'retrieval', 'golden trace'])) return 'ai-eval';
|
|
213
|
+
if (containsAny(text, ['api', 'sdk', 'contract', 'status code', 'error body', 'openapi', 'consumer'])) return 'api-contract';
|
|
214
|
+
if (containsAny(text, ['pipeline', 'etl', 'elt', 'backfill', 'freshness', 'data quality', 'warehouse'])) return 'data-pipeline';
|
|
215
|
+
if (containsAny(text, ['ui', 'ux', 'screen', 'browser', 'playwright', 'responsive', 'keyboard', 'accessibility', 'visual'])) return 'web-ui';
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function classifySecurityFlavor(request = '') {
|
|
220
|
+
const text = String(request).toLowerCase();
|
|
221
|
+
if (containsAny(text, ['prompt injection', 'agent', 'rag', 'retrieval', 'embedding', 'model', 'tool scoping'])) return 'ai';
|
|
222
|
+
if (containsAny(text, ['privacy', 'pii', 'retention', 'deletion', 'consent', 'telemetry', 'trace', 'export'])) return 'privacy';
|
|
223
|
+
if (containsAny(text, ['dependency', 'package', 'supply chain', 'sbom', 'provenance', 'ci permission', 'signing'])) return 'supply-chain';
|
|
224
|
+
if (containsAny(text, ['cloud', 'iam', 'bucket', 'network policy', 'encryption', 'public access', 'drift'])) return 'cloud';
|
|
225
|
+
if (containsAny(text, ['auth', 'authorization', 'xss', 'csrf', 'ssrf', 'injection', 'input validation', 'jwt'])) return 'appsec';
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function classifyDataAnalystFlavor(request = '') {
|
|
230
|
+
const text = String(request).toLowerCase();
|
|
231
|
+
if (containsAny(text, ['customer signal', 'customer notes', 'evidence brief', 'product intelligence', 'field notes', 'signal brief'])) return 'product-intelligence';
|
|
232
|
+
if (containsAny(text, ['experiment', 'a/b', 'ab test', 'randomization', 'sample size', 'mde'])) return 'experiment';
|
|
233
|
+
if (containsAny(text, ['telemetry', 'trace', 'logs', 'dashboard', 'observability', 'denominator'])) return 'telemetry';
|
|
234
|
+
if (containsAny(text, ['metric', 'funnel', 'activation', 'adoption', 'retention', 'conversion', 'guardrail'])) return 'product';
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export function classifyDataEngineerFlavor(request = '') {
|
|
239
|
+
const text = String(request).toLowerCase();
|
|
240
|
+
if (containsAny(text, ['vector', 'embedding', 'retrieval', 'hybrid search', 'semantic search', 'pgvector'])) return 'vector-retrieval';
|
|
241
|
+
if (containsAny(text, ['warehouse', 'metric layer', 'semantic layer', 'dimensional', 'partition', 'incremental model'])) return 'warehouse';
|
|
242
|
+
if (containsAny(text, ['pipeline', 'etl', 'elt', 'streaming', 'backfill', 'idempotent', 'data contract'])) return 'pipeline';
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export function classifyEngineerFlavor(request = '') {
|
|
247
|
+
const text = String(request).toLowerCase();
|
|
248
|
+
if (containsAny(text, ['llm', 'agent', 'prompt', 'eval', 'hallucination', 'rag', 'model behavior', 'model routing'])) return 'ai';
|
|
249
|
+
if (containsAny(text, ['vector', 'embedding', 'retrieval', 'warehouse', 'etl', 'elt', 'pipeline', 'streaming', 'data contract'])) return 'data';
|
|
250
|
+
if (containsAny(text, ['ci/cd', ' ci ', 'build pipeline', 'build system', 'deploy', 'docker', 'kubernetes', 'terraform', 'helm', 'release pipeline', 'platform tooling', 'developer experience'])) return 'platform';
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function isDataAnalysisRequest(request = '') {
|
|
255
|
+
return Boolean(classifyDataAnalystFlavor(request));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function isDataEngineeringRequest(request = '') {
|
|
259
|
+
return Boolean(classifyDataEngineerFlavor(request));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Activation detectors for specialists without a flavor classifier. Each maps
|
|
263
|
+
// a request to a keyword signature; an empty match leaves the specialist
|
|
264
|
+
// dormant unless an event in EVENT_OWNERSHIP fires.
|
|
265
|
+
|
|
266
|
+
export function isLegalComplianceRequest(request = '') {
|
|
267
|
+
const text = String(request).toLowerCase();
|
|
268
|
+
return containsAny(text, [
|
|
269
|
+
'legal review', 'compliance review', 'regulatory', 'gdpr', 'ccpa', 'hipaa',
|
|
270
|
+
'soc 2', 'soc2', 'data processing agreement', 'dpa', 'terms of service',
|
|
271
|
+
'license compliance', 'open-source license', 'attribution requirement',
|
|
272
|
+
'privacy policy', 'consent flow', 'data residency', 'export control',
|
|
273
|
+
]);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function isBusinessStrategyRequest(request = '') {
|
|
277
|
+
const text = String(request).toLowerCase();
|
|
278
|
+
return containsAny(text, [
|
|
279
|
+
'go-to-market', 'gtm strategy', 'market positioning', 'competitive analysis',
|
|
280
|
+
'business case', 'value proposition', 'pricing strategy', 'market segmentation',
|
|
281
|
+
'investment thesis', 'strategic direction',
|
|
282
|
+
]);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function isOperationsPlanningRequest(request = '') {
|
|
286
|
+
const text = String(request).toLowerCase();
|
|
287
|
+
return containsAny(text, [
|
|
288
|
+
'dependency sequencing', 'critical path', 'milestone plan',
|
|
289
|
+
'resource allocation', 'capacity planning', 'roadmap sequencing',
|
|
290
|
+
'cross-team dependency', 'multi-quarter plan', 'rollout sequencing',
|
|
291
|
+
]);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export function isRdLeadRequest(request = '') {
|
|
295
|
+
const text = String(request).toLowerCase();
|
|
296
|
+
return containsAny(text, [
|
|
297
|
+
'hypothesis', 'falsifiable', 'research question', 'experimental design',
|
|
298
|
+
'technology spike', 'feasibility study', 'proof of concept', 'r&d',
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function isExplorerRequest(request = '') {
|
|
303
|
+
const text = String(request).toLowerCase();
|
|
304
|
+
return containsAny(text, [
|
|
305
|
+
'explore the', 'spike', 'walkthrough', 'code walk', 'scoping pass',
|
|
306
|
+
'recon', 'reconnaissance', 'survey the code', 'orient me',
|
|
307
|
+
]);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function isVisualDeliverableRequest(request = '') {
|
|
311
|
+
const text = String(request).toLowerCase();
|
|
312
|
+
return containsAny(text, [
|
|
313
|
+
'wireframe',
|
|
314
|
+
'diagram',
|
|
315
|
+
'flowchart',
|
|
316
|
+
'mermaid',
|
|
317
|
+
'sequence diagram',
|
|
318
|
+
'state diagram',
|
|
319
|
+
'er diagram',
|
|
320
|
+
'mockup',
|
|
321
|
+
'storyboard',
|
|
322
|
+
'deck',
|
|
323
|
+
'slide deck',
|
|
324
|
+
'slides',
|
|
325
|
+
'presentation',
|
|
326
|
+
'powerpoint',
|
|
327
|
+
'ppt',
|
|
328
|
+
'pptx',
|
|
329
|
+
'walkthrough video',
|
|
330
|
+
'demo video',
|
|
331
|
+
]);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export function classifyRoleFlavors(request = '') {
|
|
335
|
+
const engineerFlavor = classifyEngineerFlavor(request);
|
|
336
|
+
const dataEngineerFlavor = classifyDataEngineerFlavor(request);
|
|
337
|
+
return {
|
|
338
|
+
engineer: engineerFlavor && !['ai', 'platform', 'data'].includes(engineerFlavor) ? engineerFlavor : null,
|
|
339
|
+
aiEngineer: engineerFlavor === 'ai' ? 'core' : null,
|
|
340
|
+
platformEngineer: engineerFlavor === 'platform' ? 'core' : null,
|
|
341
|
+
architect: classifyArchitectFlavor(request),
|
|
342
|
+
productManager: isProductIntelligenceRequest(request) ? classifyProductManagerFlavor(request) : null,
|
|
343
|
+
qa: classifyQaFlavor(request),
|
|
344
|
+
security: classifySecurityFlavor(request),
|
|
345
|
+
dataAnalyst: classifyDataAnalystFlavor(request),
|
|
346
|
+
dataEngineer: dataEngineerFlavor,
|
|
347
|
+
businessStrategist: isBusinessStrategyRequest(request) ? 'core' : null,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// One line per non-null flavor: "cx-<role>: loaded <role>.<flavor>
|
|
352
|
+
// overlay". Verbose host surfaces + cx_trace span attribute; lets a
|
|
353
|
+
// post-hoc reviewer see which overlays drove a given dispatch.
|
|
354
|
+
|
|
355
|
+
export function formatOverlaySelection(roleFlavors) {
|
|
356
|
+
if (!roleFlavors || typeof roleFlavors !== 'object') return [];
|
|
357
|
+
const lines = [];
|
|
358
|
+
for (const [classifierKey, flavor] of Object.entries(roleFlavors)) {
|
|
359
|
+
const line = formatOverlayLine(classifierKey, flavor);
|
|
360
|
+
if (line) lines.push(line);
|
|
361
|
+
}
|
|
362
|
+
return lines;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function detectRiskFlags(request = '') {
|
|
366
|
+
const text = String(request).toLowerCase();
|
|
367
|
+
return {
|
|
368
|
+
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']),
|
|
369
|
+
security: containsAny(text, ['security', 'permission', 'secret', 'privacy', 'payment', 'authentication', 'authorization', 'credential', 'oidc', 'iam', 'access token']),
|
|
370
|
+
dataIntegrity: containsAny(text, ['migration', 'data', 'sync', 'consistency', 'state', 'tfstate', 'drift']),
|
|
371
|
+
ui: containsAny(text, ['ui', 'ux', 'design system', 'screen', 'layout', 'visual', 'onboarding']) && !containsAny(text, ['requirements']),
|
|
372
|
+
docs: containsAny(text, ['docs', 'readme', 'runbook', 'adr']),
|
|
373
|
+
ai: containsAny(text, ['llm', ' agent', 'prompt', 'rag', 'model behavior', 'retrieval', 'embedding', 'vector']),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export function classifyIntent(request = '') {
|
|
378
|
+
const text = String(request).toLowerCase();
|
|
379
|
+
if (containsAny(text, ['fix', 'bug', 'regression', 'broken', 'repair'])) return INTENT_CLASSES.fix;
|
|
380
|
+
if (includesAny(text, [/debug/, /investigate/, /trace/, /root cause/, /why .* fail/])) return INTENT_CLASSES.investigation;
|
|
381
|
+
if (isProductIntelligenceRequest(text)) return INTENT_CLASSES.implementation;
|
|
382
|
+
if (includesAny(text, [/review/, /audit/, /validate/, /ready to ship/, /check/])) return INTENT_CLASSES.evaluation;
|
|
383
|
+
if (includesAny(text, [/research/, /explore/, /compare/, /what does/, /explain/, /understand/, /docs?/])) return INTENT_CLASSES.research;
|
|
384
|
+
return INTENT_CLASSES.implementation;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function classifyWorkCategory(request = '', riskFlags = detectRiskFlags(request)) {
|
|
388
|
+
const text = String(request).toLowerCase();
|
|
389
|
+
if (riskFlags.ui || isVisualDeliverableRequest(text)) return WORK_CATEGORIES.visual;
|
|
390
|
+
if (riskFlags.docs || containsAny(text, ['write', 'rewrite', 'document', 'spec', 'requirements'])) return WORK_CATEGORIES.writing;
|
|
391
|
+
if (includesAny(text, [/analy[sz]e/, /measure/, /metrics/, /score/, /evaluate/])) return WORK_CATEGORIES.analysis;
|
|
392
|
+
if (riskFlags.architecture || riskFlags.ai || includesAny(text, [/plan/, /strategy/, /system/, /refactor/, /orchestr/])) return WORK_CATEGORIES.deep;
|
|
393
|
+
return WORK_CATEGORIES.quick;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
export function determineExecutionTrack({
|
|
397
|
+
request = '',
|
|
398
|
+
fileCount = 0,
|
|
399
|
+
moduleCount = 0,
|
|
400
|
+
introducesContract = false,
|
|
401
|
+
explicitDrive = false,
|
|
402
|
+
riskFlags = detectRiskFlags(request),
|
|
403
|
+
} = {}) {
|
|
404
|
+
const intent = classifyIntent(request);
|
|
405
|
+
const workCategory = classifyWorkCategory(request, riskFlags);
|
|
406
|
+
const text = String(request).toLowerCase();
|
|
407
|
+
const localWalkthrough = isExplorerRequest(request)
|
|
408
|
+
|| /\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)
|
|
409
|
+
|| /\bhow\b[\s\S]{0,40}\bworks?\b/i.test(text);
|
|
410
|
+
if (explicitDrive) return EXECUTION_TRACKS.orchestrated;
|
|
411
|
+
if (intent === INTENT_CLASSES.research) {
|
|
412
|
+
if (localWalkthrough && fileCount <= 1 && moduleCount <= 1) return EXECUTION_TRACKS.immediate;
|
|
413
|
+
if (fileCount <= 1 && moduleCount <= 1) return EXECUTION_TRACKS.focused;
|
|
414
|
+
return EXECUTION_TRACKS.orchestrated;
|
|
415
|
+
}
|
|
416
|
+
if (introducesContract || fileCount >= 3 || moduleCount >= 2) return EXECUTION_TRACKS.orchestrated;
|
|
417
|
+
if (riskFlags.architecture || riskFlags.security || riskFlags.dataIntegrity || riskFlags.ai) return EXECUTION_TRACKS.orchestrated;
|
|
418
|
+
if (workCategory === WORK_CATEGORIES.visual) return EXECUTION_TRACKS.focused;
|
|
419
|
+
// A keyword that names a specialist should activate that specialist even
|
|
420
|
+
// for small-scope work — otherwise the immediate-track fallback below
|
|
421
|
+
// silently drops the request to "answer directly," losing the specialty.
|
|
422
|
+
if (
|
|
423
|
+
isLegalComplianceRequest(request)
|
|
424
|
+
|| isBusinessStrategyRequest(request)
|
|
425
|
+
|| isOperationsPlanningRequest(request)
|
|
426
|
+
|| isRdLeadRequest(request)
|
|
427
|
+
|| isExplorerRequest(request)
|
|
428
|
+
|| requiresLiveWebAccess(request)
|
|
429
|
+
) {
|
|
430
|
+
return EXECUTION_TRACKS.focused;
|
|
431
|
+
}
|
|
432
|
+
if (fileCount <= 1 && moduleCount <= 1 && !includesAny(String(request).toLowerCase(), [/end to end/, /ship/, /full/])) return EXECUTION_TRACKS.immediate;
|
|
433
|
+
return EXECUTION_TRACKS.focused;
|
|
434
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/orchestration/delegation-flow.mjs — flow-engine delegation chain for
|
|
3
|
+
* orchestration-policy routes (ADR-0067, construct-rf26.9).
|
|
4
|
+
*
|
|
5
|
+
* Converts a resolved orchestration-policy route (lib/orchestration-policy.mjs's
|
|
6
|
+
* routeRequest()) into a deterministic, checkpointable flow: one step per
|
|
7
|
+
* specialist in route.specialists' (or route.displaySpecialists') order, each
|
|
8
|
+
* step's run() producing exactly the delegation spec — role, reason,
|
|
9
|
+
* handoffContract — that lib/orchestration/runtime.mjs's buildTasks() already
|
|
10
|
+
* computes per task. This mirrors the one-task-at-a-time granularity that path
|
|
11
|
+
* already gets right rather than inventing a new delegation shape; it is the
|
|
12
|
+
* sequencing itself (which specialist is current, and when the chain ends)
|
|
13
|
+
* that moves from freeform dispatchPlan/dispatchSummary prose into a real
|
|
14
|
+
* engine-held state machine.
|
|
15
|
+
*
|
|
16
|
+
* advanceDelegation() is the caller-facing entry point: it rebuilds the flow
|
|
17
|
+
* fresh from the route on every call — routeRequest is a pure function of its
|
|
18
|
+
* inputs, so the flow it implies is fully reproducible — and checkpoints
|
|
19
|
+
* progress under runId via lib/flows/checkpoint.mjs. A caller (an MCP tool, a
|
|
20
|
+
* CLI, a persona driving its own dispatch loop) receives exactly the current
|
|
21
|
+
* step's delegation, never the whole chain, and can resume across separate
|
|
22
|
+
* process/turn boundaries by calling again with the same runId.
|
|
23
|
+
*
|
|
24
|
+
* A route with no specialists (the immediate track, or a focused/orchestrated
|
|
25
|
+
* route that happened to resolve to none) still yields a load-valid flow: a
|
|
26
|
+
* single terminal step whose delegation is null, so building the flow never
|
|
27
|
+
* throws on an empty chain.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { defineFlow } from '../flows/define.mjs';
|
|
31
|
+
import { TERMINAL, RUN_STATUS } from '../flows/constants.mjs';
|
|
32
|
+
import { tickCheckpointed } from '../flows/checkpoint.mjs';
|
|
33
|
+
|
|
34
|
+
const STATE_SCHEMA = {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
currentDelegation: { type: ['object', 'null'] },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Mirrors lib/orchestration/runtime.mjs's buildTasks(): the actual dispatch
|
|
42
|
+
// order is route.specialists, not route.displaySpecialists (the latter is a
|
|
43
|
+
// display-only fallback for an immediate-track route's contractChain/
|
|
44
|
+
// teamRouting computation — it is never what gets dispatched).
|
|
45
|
+
|
|
46
|
+
function resolveSpecialists(route) {
|
|
47
|
+
return route.specialists || [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Mirrors lib/orchestration/runtime.mjs's buildTasks(): the first contract
|
|
51
|
+
// chain edge for a given producer is the handoff contract that specialist
|
|
52
|
+
// hands off under. Recomputed here rather than imported so this module has
|
|
53
|
+
// no runtime dependency beyond the route object itself.
|
|
54
|
+
|
|
55
|
+
function handoffContractsByProducer(route) {
|
|
56
|
+
const byProducer = new Map();
|
|
57
|
+
for (const edge of route.contractChain || []) {
|
|
58
|
+
const producer = edge.contract?.producer;
|
|
59
|
+
if (producer && !byProducer.has(producer)) byProducer.set(producer, edge.contract.id);
|
|
60
|
+
}
|
|
61
|
+
return byProducer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build (not run) the flow definition implied by a route. Pure function of
|
|
66
|
+
* `route` — same route in, same flow shape out — so a caller never needs to
|
|
67
|
+
* persist the flow itself, only the runId tracking progress through it.
|
|
68
|
+
*/
|
|
69
|
+
export function buildDelegationFlow(route, { id } = {}) {
|
|
70
|
+
const specialists = resolveSpecialists(route);
|
|
71
|
+
const reasons = route.dispatchReasons || {};
|
|
72
|
+
const handoffByProducer = handoffContractsByProducer(route);
|
|
73
|
+
const flowId = id ?? `delegation-${route.intent ?? 'unknown'}-${route.track ?? 'unknown'}`;
|
|
74
|
+
|
|
75
|
+
if (specialists.length === 0) {
|
|
76
|
+
return defineFlow({
|
|
77
|
+
id: flowId,
|
|
78
|
+
stateSchema: STATE_SCHEMA,
|
|
79
|
+
startStep: 'none',
|
|
80
|
+
steps: {
|
|
81
|
+
none: { workerBackend: 'inline', run: () => ({ state: { currentDelegation: null } }), router: () => TERMINAL },
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const names = specialists.map((_, i) => `step_${i}`);
|
|
87
|
+
const steps = {};
|
|
88
|
+
specialists.forEach((role, i) => {
|
|
89
|
+
const nextName = names[i + 1] ?? null;
|
|
90
|
+
steps[names[i]] = {
|
|
91
|
+
workerBackend: 'host',
|
|
92
|
+
run: () => ({
|
|
93
|
+
state: {
|
|
94
|
+
currentDelegation: {
|
|
95
|
+
role,
|
|
96
|
+
reason: reasons[role] || null,
|
|
97
|
+
handoffContract: handoffByProducer.get(role.replace(/^cx-/, '')) || null,
|
|
98
|
+
index: i,
|
|
99
|
+
total: specialists.length,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
router: () => nextName ?? TERMINAL,
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return defineFlow({ id: flowId, stateSchema: STATE_SCHEMA, startStep: names[0], steps });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Advance a route's implied delegation chain by exactly one step, checkpointed
|
|
112
|
+
* under runId. First call (no checkpoint yet) starts the run and executes its
|
|
113
|
+
* first step in the same tick, so the caller always gets a real delegation
|
|
114
|
+
* back rather than an empty "not started" state. Every subsequent call with
|
|
115
|
+
* the same runId advances to the next specialist. `done: true` once the chain
|
|
116
|
+
* is exhausted; `currentDelegation` is null only for an immediate-track route
|
|
117
|
+
* that resolved to no specialists at all.
|
|
118
|
+
*/
|
|
119
|
+
export async function advanceDelegation(cwd, runId, route) {
|
|
120
|
+
const flow = buildDelegationFlow(route);
|
|
121
|
+
const run = await tickCheckpointed(cwd, runId, flow, {});
|
|
122
|
+
const specialists = resolveSpecialists(route);
|
|
123
|
+
return {
|
|
124
|
+
runId,
|
|
125
|
+
flowId: flow.id,
|
|
126
|
+
status: run.status,
|
|
127
|
+
done: run.status !== RUN_STATUS.RUNNING,
|
|
128
|
+
currentDelegation: run.state?.currentDelegation ?? null,
|
|
129
|
+
stepsCompleted: run.completed.size,
|
|
130
|
+
totalSteps: specialists.length,
|
|
131
|
+
};
|
|
132
|
+
}
|