@geraldmaron/construct 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -13,19 +13,60 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { runOrchestration, startRun, getRun, getRuns } from '../../orchestration/runtime.mjs';
|
|
16
|
+
import { HOST } from '../../orchestration/worker.mjs';
|
|
17
|
+
import { loadProjectConfig } from '../../config/project-config.mjs';
|
|
16
18
|
import { governWebResults } from './web-search-governance.mjs';
|
|
19
|
+
import { resolveNonNegativeSetting } from '../../env-config.mjs';
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
// Host loop instructions echoed on an awaiting-host run so the calling agent
|
|
22
|
+
// never has to guess the protocol: execute each materialized prompt as that
|
|
23
|
+
// specialist, submit the result, and follow next_task until it is null.
|
|
24
|
+
// Worded to be un-misreadable as a failure: a VS Code host once read "did not
|
|
25
|
+
// execute" as prepare-only-and-stuck, retried with worker_backend "provider",
|
|
26
|
+
// and died on missing API credits — so the text must lead with success and
|
|
27
|
+
// explicitly forbid that fallback.
|
|
28
|
+
|
|
29
|
+
const HOST_INSTRUCTIONS =
|
|
30
|
+
'ACTION REQUIRED — this run succeeded and is now waiting on YOU, the calling agent. The host worker '
|
|
31
|
+
+ 'backend means Construct materialized each specialist prompt and you execute them in your own '
|
|
32
|
+
+ 'session (no API credits are spent). This is the normal path, not a failure: do NOT re-run with '
|
|
33
|
+
+ 'worker_backend "provider" (real API spend, needs provider credits) and do not report the run as '
|
|
34
|
+
+ 'incomplete. For each task in `tasks`, execute its `system`/`user` prompt yourself as that '
|
|
35
|
+
+ 'specialist role, in order, then submit the result by calling orchestration_task_result with '
|
|
36
|
+
+ '{ run_id, task_id, output }. Its response carries next_task (the next prompt to execute) or null '
|
|
37
|
+
+ 'once the run is terminal — keep submitting until next_task is null, then re-read the run via '
|
|
38
|
+
+ 'orchestration_status if you need the final consolidated output.';
|
|
39
|
+
|
|
40
|
+
// Every state a run can rest in. `degraded` and `completed-prepare-only` are
|
|
41
|
+
// terminal too — the poll loop must stop on them or a finished degraded/prepare-only
|
|
42
|
+
// remote run reads as "still executing" until the overall deadline elapses.
|
|
43
|
+
|
|
44
|
+
const TERMINAL = ['completed', 'completed-with-failures', 'completed-prepare-only', 'degraded', 'cancelled', 'error'];
|
|
45
|
+
|
|
46
|
+
export function shapeRun(run) {
|
|
47
|
+
const hasTasks = Array.isArray(run.tasks) && run.tasks.length > 0;
|
|
48
|
+
const allPrepared = hasTasks && run.tasks.every((t) => t.status === 'prepared');
|
|
49
|
+
const degraded = run.degraded ?? run.execution?.degraded ?? false;
|
|
50
|
+
|
|
51
|
+
// Terminal-status taxonomy (construct-fbxv.1): a degraded run never surfaces as a
|
|
52
|
+
// bare 'completed'. All-prepared is the more specific "no specialist executed"
|
|
53
|
+
// signal, so it wins; otherwise any degraded run — whether it prepared zero tasks
|
|
54
|
+
// or executed with a capability gap — reports 'degraded'. Legacy runs persisted as
|
|
55
|
+
// bare 'completed' before this taxonomy are re-derived the same way.
|
|
56
|
+
const shapedStatus = allPrepared
|
|
57
|
+
? 'completed-prepare-only'
|
|
58
|
+
: degraded && run.status === 'completed'
|
|
59
|
+
? 'degraded'
|
|
60
|
+
: run.status;
|
|
19
61
|
|
|
20
|
-
function shapeRun(run) {
|
|
21
|
-
const allPrepared = Array.isArray(run.tasks) && run.tasks.length > 0 && run.tasks.every((t) => t.status === 'prepared');
|
|
22
62
|
return {
|
|
23
63
|
runId: run.runId,
|
|
24
|
-
status:
|
|
64
|
+
status: shapedStatus,
|
|
25
65
|
prepareOnly: allPrepared,
|
|
66
|
+
runMode: run.runMode ?? null,
|
|
26
67
|
semantics: run.semantics ?? null,
|
|
27
68
|
executionMode: run.execution?.executionMode,
|
|
28
|
-
degraded
|
|
69
|
+
degraded,
|
|
29
70
|
degradationReason: run.degradationReason ?? run.execution?.degradationReason ?? null,
|
|
30
71
|
intent: run.plan?.intent ?? null,
|
|
31
72
|
track: run.plan?.track ?? null,
|
|
@@ -35,10 +76,15 @@ function shapeRun(run) {
|
|
|
35
76
|
tasks: (run.tasks || []).map((t) => ({
|
|
36
77
|
id: t.id, role: t.role, status: t.status, executor: t.executor,
|
|
37
78
|
output: t.output ?? null, reasoning: t.reasoning ?? null, error: t.error ?? null,
|
|
38
|
-
// ADR-0050: which web grant reached the web, the F08-governed evidence, and search count.
|
|
39
79
|
webCapability: t.webCapability ?? null,
|
|
40
80
|
webEvidence: t.webEvidence ?? null,
|
|
41
81
|
webSearchRequests: t.webSearchRequests ?? 0,
|
|
82
|
+
// Host-backend fields only: the materialized prompt an awaiting-host task
|
|
83
|
+
// carries (nulled out once submitHostTaskResult records a real result),
|
|
84
|
+
// and provenanceSource — present only on a host-reported result, so it can
|
|
85
|
+
// never be confused with a construct-verified provider execution.
|
|
86
|
+
...(t.hostPrompt ? { system: t.hostPrompt.system, user: t.hostPrompt.user } : {}),
|
|
87
|
+
...(t.provenanceSource ? { provenanceSource: t.provenanceSource } : {}),
|
|
42
88
|
})),
|
|
43
89
|
};
|
|
44
90
|
}
|
|
@@ -75,6 +121,27 @@ function toRequest(args) {
|
|
|
75
121
|
};
|
|
76
122
|
}
|
|
77
123
|
|
|
124
|
+
// Only MCP dispatch reaches orchestrationRun() — an in-process CLI call goes
|
|
125
|
+
// through runOrchestration/planRun directly (see bin/construct's `orchestrate
|
|
126
|
+
// run`), never through this function — so the host-execution default belongs
|
|
127
|
+
// exactly here: when the caller gave no explicit worker_backend AND the
|
|
128
|
+
// project's own construct.config.json declares none, an MCP-originated run
|
|
129
|
+
// defaults to the host executing specialists in its own session rather than
|
|
130
|
+
// inline prepare-only or a provider API spend the caller may have no credits
|
|
131
|
+
// for. The RAW (pre-default-merge) project config is the check that matters —
|
|
132
|
+
// loadProjectConfig always merges DEFAULT_PROJECT_CONFIG in, so the merged
|
|
133
|
+
// config's workerBackend is never actually absent; only the raw file reveals
|
|
134
|
+
// whether the user configured one.
|
|
135
|
+
|
|
136
|
+
function resolveMcpDefaultBackend(explicitBackend, { cwd, env }) {
|
|
137
|
+
if (explicitBackend) return explicitBackend;
|
|
138
|
+
try {
|
|
139
|
+
const { raw } = loadProjectConfig(cwd, env);
|
|
140
|
+
if (raw?.orchestration?.workerBackend) return undefined;
|
|
141
|
+
} catch { /* no resolvable config — fall through to the host default */ }
|
|
142
|
+
return HOST;
|
|
143
|
+
}
|
|
144
|
+
|
|
78
145
|
// A remote orchestration service is opt-in: only when CONSTRUCT_ORCHESTRATION_URL
|
|
79
146
|
// is set (team / enterprise). The token comes from the Construct env, not from
|
|
80
147
|
// the dashboard server, so the in-process default path carries no dashboard coupling.
|
|
@@ -94,6 +161,17 @@ function unreachable(base, detail) {
|
|
|
94
161
|
};
|
|
95
162
|
}
|
|
96
163
|
|
|
164
|
+
// A bounded fetch timing out is not the same failure as a genuinely unreachable host
|
|
165
|
+
// (ORCH-004 AC3): the error text must say timeout and name the bound so a healthy but
|
|
166
|
+
// slow service is never misreported as network-down.
|
|
167
|
+
|
|
168
|
+
function remoteFetchFailed(base, err) {
|
|
169
|
+
if (err?.name === 'RemoteFetchTimeoutError') {
|
|
170
|
+
return { error: `${err.message}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, service: base, failFast: true };
|
|
171
|
+
}
|
|
172
|
+
return unreachable(base, err.message);
|
|
173
|
+
}
|
|
174
|
+
|
|
97
175
|
export async function orchestrationRun(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
|
|
98
176
|
const { request, worker_backend, wait = true, timeout_ms = 120000 } = args;
|
|
99
177
|
if (!request || typeof request !== 'string') {
|
|
@@ -102,15 +180,17 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
|
|
|
102
180
|
|
|
103
181
|
const reqObj = toRequest(args);
|
|
104
182
|
const remote = remoteService(env);
|
|
105
|
-
if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl });
|
|
183
|
+
if (remote) return runViaService(remote, reqObj, { wait, timeout_ms, workerBackend: worker_backend, fetchImpl, env });
|
|
106
184
|
|
|
107
|
-
const
|
|
185
|
+
const workerBackend = resolveMcpDefaultBackend(worker_backend, { cwd, env });
|
|
186
|
+
const opts = { env, cwd, workerBackend, fetchImpl };
|
|
108
187
|
try {
|
|
109
188
|
if (!wait) {
|
|
110
189
|
const planned = await startRun(reqObj, opts);
|
|
111
190
|
return { runId: planned.runId, status: planned.status, executionMode: planned.execution?.executionMode, poll: 'orchestration_status with run_id' };
|
|
112
191
|
}
|
|
113
|
-
|
|
192
|
+
const shaped = shapeRun(await runOrchestration(reqObj, opts));
|
|
193
|
+
return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
|
|
114
194
|
} catch (err) {
|
|
115
195
|
return { error: `Orchestration failed: ${err.message}`, failFast: true };
|
|
116
196
|
}
|
|
@@ -119,13 +199,16 @@ export async function orchestrationRun(args = {}, { env = process.env, cwd = pro
|
|
|
119
199
|
export async function orchestrationStatus(args = {}, { env = process.env, cwd = process.cwd(), fetchImpl = fetch } = {}) {
|
|
120
200
|
const { run_id, limit = 20 } = args;
|
|
121
201
|
const remote = remoteService(env);
|
|
122
|
-
if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl });
|
|
202
|
+
if (remote) return statusViaService(remote, { run_id, limit }, { fetchImpl, env });
|
|
123
203
|
try {
|
|
124
204
|
if (run_id) {
|
|
125
205
|
const run = await getRun(cwd, run_id, { env });
|
|
126
|
-
|
|
206
|
+
if (!run) return { error: `No run found with id ${run_id}.` };
|
|
207
|
+
const shaped = shapeRun(run);
|
|
208
|
+
return shaped.status === 'awaiting-host' ? { ...shaped, hostInstructions: HOST_INSTRUCTIONS } : shaped;
|
|
127
209
|
}
|
|
128
|
-
|
|
210
|
+
const runs = await getRuns(cwd, { env, limit });
|
|
211
|
+
return runs.map(shapeRun);
|
|
129
212
|
} catch (err) {
|
|
130
213
|
return { error: `Failed to read orchestration runs: ${err.message}` };
|
|
131
214
|
}
|
|
@@ -135,18 +218,58 @@ export async function orchestrationStatus(args = {}, { env = process.env, cwd =
|
|
|
135
218
|
// is set; the wire shape matches the standalone orchestration service's
|
|
136
219
|
// /api/orchestration/runs contract (ADR-0022).
|
|
137
220
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
221
|
+
// Reachability fail-fast belongs to the deadline check in the poll loop, not the
|
|
222
|
+
// per-request budget: a remote LLM-backed run needs a generous per-request window
|
|
223
|
+
// (ORCH-004; construct-o6t8.2). Default is 30s, well above the industry reachability-probe
|
|
224
|
+
// mistake this replaces, and short of the 600s ceiling the OpenAI/Anthropic SDKs use for
|
|
225
|
+
// long completions — an orchestration run/status round trip is shorter-lived than that.
|
|
226
|
+
// Env-overridable via the shared non-negative resolver so an explicit 0 is honored
|
|
227
|
+
// (near-instant abort) rather than silently treated as unset, and garbage/negative
|
|
228
|
+
// values fall back to the default instead of producing a NaN AbortSignal delay.
|
|
229
|
+
|
|
230
|
+
export const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
|
|
231
|
+
|
|
232
|
+
function requestTimeoutMs(env) {
|
|
233
|
+
return resolveNonNegativeSetting(env, 'CONSTRUCT_ORCHESTRATION_TIMEOUT_MS', DEFAULT_REQUEST_TIMEOUT_MS);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Per-request bound on every remote fetch (ORCH-004; construct-o6t8.2 / construct-neq9.6):
|
|
237
|
+
// a timer aborts the request at perRequestMs and races it against a timeout rejection, so
|
|
238
|
+
// a hung request settles the call even when the underlying fetch ignores its own signal.
|
|
239
|
+
// The timer is cleared the instant either side of the race wins, so a healthy fast response
|
|
240
|
+
// leaves no scheduled timer alive to fire and resolve a promise outside its own async scope
|
|
241
|
+
// — the leak that tripped node:test's "resolution still pending after the loop resolved". The
|
|
242
|
+
// rejection carries the numbered RemoteFetchTimeoutError so a bounded timeout stays
|
|
243
|
+
// distinguishable from a genuinely unreachable host, never the signal's own DOMException
|
|
244
|
+
// message ("aborted due to timeout" with no number).
|
|
245
|
+
|
|
246
|
+
class RemoteFetchTimeoutError extends Error {
|
|
247
|
+
constructor(perRequestMs) {
|
|
248
|
+
super(`Remote orchestration request timed out after ${perRequestMs}ms`);
|
|
249
|
+
this.name = 'RemoteFetchTimeoutError';
|
|
250
|
+
this.timeoutMs = perRequestMs;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
140
253
|
|
|
141
|
-
function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
254
|
+
async function timedRemoteFetch(fetchImpl, url, opts, perRequestMs) {
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
let clearTimer;
|
|
257
|
+
const timeout = new Promise((_, reject) => {
|
|
258
|
+
const timer = setTimeout(() => {
|
|
259
|
+
const err = new RemoteFetchTimeoutError(perRequestMs);
|
|
260
|
+
controller.abort(err);
|
|
261
|
+
reject(err);
|
|
262
|
+
}, perRequestMs);
|
|
263
|
+
clearTimer = () => clearTimeout(timer);
|
|
264
|
+
});
|
|
265
|
+
try {
|
|
266
|
+
return await Promise.race([fetchImpl(url, { ...opts, signal: controller.signal }), timeout]);
|
|
267
|
+
} finally {
|
|
268
|
+
clearTimer();
|
|
269
|
+
}
|
|
147
270
|
}
|
|
148
271
|
|
|
149
|
-
async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, perRequestTimeoutMs =
|
|
272
|
+
async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend, fetchImpl, env = process.env, perRequestTimeoutMs = requestTimeoutMs(env) } = {}) {
|
|
150
273
|
const { base, token } = remote;
|
|
151
274
|
const headers = { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) };
|
|
152
275
|
const body = JSON.stringify({ ...reqObj, workerBackend });
|
|
@@ -160,7 +283,7 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
|
|
|
160
283
|
if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
|
|
161
284
|
started = envelope.data;
|
|
162
285
|
} catch (err) {
|
|
163
|
-
return
|
|
286
|
+
return remoteFetchFailed(base, err);
|
|
164
287
|
}
|
|
165
288
|
|
|
166
289
|
if (!wait) {
|
|
@@ -180,22 +303,39 @@ async function runViaService(remote, reqObj, { wait, timeout_ms, workerBackend,
|
|
|
180
303
|
if (!res.ok) return { error: `Remote service error while polling (HTTP ${res.status}): ${envelope.error || 'unknown'}`, runId: started.runId, service: base };
|
|
181
304
|
run = envelope.data;
|
|
182
305
|
} catch (err) {
|
|
306
|
+
if (err?.name === 'RemoteFetchTimeoutError') {
|
|
307
|
+
return { error: `${err.message} while polling run ${started.runId}. Raise CONSTRUCT_ORCHESTRATION_TIMEOUT_MS if the remote service is healthy but slow.`, runId: started.runId, service: base, failFast: true };
|
|
308
|
+
}
|
|
183
309
|
return { error: `Lost connection to the remote service while polling run ${started.runId}: ${err.message}`, runId: started.runId, service: base, failFast: true };
|
|
184
310
|
}
|
|
185
311
|
}
|
|
186
312
|
return { ...shapeRun(governRemoteWebEvidence(run)), service: base };
|
|
187
313
|
}
|
|
188
314
|
|
|
189
|
-
|
|
315
|
+
// The status poll GET carries the same AbortSignal bound as the run POST (ORCH-004),
|
|
316
|
+
// so a service that accepts the connection but never replies cannot hang this call
|
|
317
|
+
// indefinitely. `timeoutMs` is overridable directly for tests; production callers
|
|
318
|
+
// resolve it from env the same way runViaService does.
|
|
319
|
+
|
|
320
|
+
export async function statusViaService(remote, { run_id, limit } = {}, { fetchImpl, env = process.env, timeoutMs = requestTimeoutMs(env) } = {}) {
|
|
190
321
|
const { base, token } = remote;
|
|
191
322
|
const headers = token ? { Authorization: `Bearer ${token}` } : {};
|
|
192
323
|
const path = run_id ? `/api/orchestration/runs/${encodeURIComponent(run_id)}` : `/api/orchestration/runs?limit=${encodeURIComponent(limit)}`;
|
|
193
324
|
try {
|
|
194
|
-
const res = await fetchImpl
|
|
325
|
+
const res = await timedRemoteFetch(fetchImpl, `${base}${path}`, { headers }, timeoutMs);
|
|
195
326
|
const envelope = await res.json();
|
|
196
327
|
if (!res.ok) return { error: `Remote service error (HTTP ${res.status}): ${envelope.error || 'unknown'}`, service: base };
|
|
328
|
+
// The remote service is out-of-repo and cannot be trusted to have governed its
|
|
329
|
+
// own web evidence (ADR-0050); re-run every run through the F08 grader on the
|
|
330
|
+
// status path exactly as runViaService does, so a citation can never arrive
|
|
331
|
+
// pre-marked trusted. shapeRun then applies the honest terminal-status taxonomy.
|
|
332
|
+
if (Array.isArray(envelope.data)) {
|
|
333
|
+
return envelope.data.map((r) => shapeRun(governRemoteWebEvidence(r)));
|
|
334
|
+
} else if (envelope.data) {
|
|
335
|
+
return shapeRun(governRemoteWebEvidence(envelope.data));
|
|
336
|
+
}
|
|
197
337
|
return envelope.data;
|
|
198
338
|
} catch (err) {
|
|
199
|
-
return
|
|
339
|
+
return remoteFetchFailed(base, err);
|
|
200
340
|
}
|
|
201
341
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tools/orchestration-task-result.tool.mjs — self-registered MCP tool
|
|
3
|
+
* (LMCP-B5, lib/mcp/tool-registry.mjs) that ingests one host-executed
|
|
4
|
+
* specialist task result for a run planned with worker_backend=host.
|
|
5
|
+
*
|
|
6
|
+
* orchestration_run materializes each specialist's prompt (system + user turn)
|
|
7
|
+
* and stands the run at 'awaiting-host' without ever calling a model itself —
|
|
8
|
+
* the calling host executes each prompt in its own session (the model/
|
|
9
|
+
* subscription it is already running under) and reports the result back here.
|
|
10
|
+
* submitHostTaskResult (lib/orchestration/runtime.mjs) is the single place that
|
|
11
|
+
* validates and records the result and finalizes the run once every task is
|
|
12
|
+
* terminal; this file only shapes that call as an MCP tool and echoes back the
|
|
13
|
+
* next materialized prompt so a host can drive the whole run in a simple loop:
|
|
14
|
+
* call orchestration_run once, then call this tool per task until next_task
|
|
15
|
+
* is null.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { submitHostTaskResult } from '../../orchestration/runtime.mjs';
|
|
19
|
+
import { shapeRun } from './orchestration-run.mjs';
|
|
20
|
+
|
|
21
|
+
export const TOOL_DEFS = [
|
|
22
|
+
{
|
|
23
|
+
name: 'orchestration_task_result',
|
|
24
|
+
description:
|
|
25
|
+
'Submit one host-executed specialist task result for an orchestration run planned with '
|
|
26
|
+
+ 'worker_backend=host (the MCP default). orchestration_run returns each task\'s materialized '
|
|
27
|
+
+ 'prompt (system/user) without executing it; execute that prompt yourself as the named '
|
|
28
|
+
+ 'specialist role, then call this tool with the run_id, task_id, and your output. The '
|
|
29
|
+
+ 'response carries next_task (the next awaiting prompt) or null once the run is terminal — '
|
|
30
|
+
+ 'loop until null. model/provider/reasoning are optional, self-reported fields (never '
|
|
31
|
+
+ 'independently verified), recorded with provenanceSource "host-reported".',
|
|
32
|
+
inputSchema: {
|
|
33
|
+
type: 'object',
|
|
34
|
+
required: ['run_id', 'task_id', 'output'],
|
|
35
|
+
properties: {
|
|
36
|
+
run_id: { type: 'string', description: 'The run id returned by orchestration_run.' },
|
|
37
|
+
task_id: { type: 'string', description: 'The task id this result answers (e.g. "t1"), from orchestration_run\'s task list or a prior call\'s next_task.' },
|
|
38
|
+
output: { type: 'string', description: 'The specialist output you produced for this task. Must be non-empty.' },
|
|
39
|
+
model: { type: 'string', description: 'Optional: the model you used to execute this task (self-reported).' },
|
|
40
|
+
provider: { type: 'string', description: 'Optional: the provider/vendor family you used (self-reported).' },
|
|
41
|
+
reasoning: { type: 'string', description: 'Optional: your reasoning/thinking for this task, if you want it disclosed.' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
outputSchema: { type: 'object' },
|
|
45
|
+
safety: { class: 'write', filesystem: 'write', network: 'none', process: 'none' },
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} args { run_id, task_id, output, model?, provider?, reasoning? }
|
|
51
|
+
* @param {object} [opts] { env, cwd } — defaulted so this behaves identically whether called
|
|
52
|
+
* directly (tests, CLI) or dispatched by the MCP server (which does not inject opts today,
|
|
53
|
+
* matching orchestration_run/orchestration_status's own convention).
|
|
54
|
+
*/
|
|
55
|
+
export async function orchestrationTaskResult(args = {}, { env = process.env, cwd = process.cwd() } = {}) {
|
|
56
|
+
const { run_id, task_id, output, model, provider, reasoning } = args;
|
|
57
|
+
if (!run_id || typeof run_id !== 'string') return { error: 'Missing "run_id" — the run id from orchestration_run.' };
|
|
58
|
+
if (!task_id || typeof task_id !== 'string') return { error: 'Missing "task_id".' };
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const { run, nextTask } = await submitHostTaskResult(cwd, run_id, task_id, { output, model, provider, reasoning }, { env });
|
|
62
|
+
const shaped = shapeRun(run);
|
|
63
|
+
return {
|
|
64
|
+
accepted: true,
|
|
65
|
+
run_status: shaped.status,
|
|
66
|
+
next_task: nextTask
|
|
67
|
+
? { task_id: nextTask.id, role: nextTask.role, system: nextTask.hostPrompt?.system ?? null, user: nextTask.hostPrompt?.user ?? null }
|
|
68
|
+
: null,
|
|
69
|
+
};
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return { accepted: false, error: err.message, code: err.code || 'HOST_RESULT_REJECTED' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const TOOL_HANDLERS = {
|
|
76
|
+
orchestration_task_result: orchestrationTaskResult,
|
|
77
|
+
};
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/mcp/tools/provider-write.mjs — provider_write MCP tool (LMCP-I7).
|
|
3
|
+
*
|
|
4
|
+
* The only MCP-reachable path from a host agent (Claude Code, OpenCode) to an
|
|
5
|
+
* external write. Classified destructive in lib/mcp/tool-safety.mjs, so
|
|
6
|
+
* server.mjs's dispatch-time checkDestructiveGate() call already refuses the
|
|
7
|
+
* execute path without a valid out-of-band approval_token before this module
|
|
8
|
+
* ever runs — this file does not re-implement that check, it is downstream
|
|
9
|
+
* of it.
|
|
10
|
+
*
|
|
11
|
+
* Two modes, selected by `dry_run` (default true):
|
|
12
|
+
* - dry_run=true: resolves the governed-write adapter for `provider` and
|
|
13
|
+
* asks it to render the payload it would submit (adapter.renderDryRun),
|
|
14
|
+
* which runs field/shape validation only. Never calls
|
|
15
|
+
* lib/writes/envelope.mjs and never reaches adapter.write() — no network
|
|
16
|
+
* call, no side effect, regardless of token presence.
|
|
17
|
+
* - dry_run=false (execute): the destructive gate has already run in
|
|
18
|
+
* server.mjs by the time dispatchToolByName reaches this module, so
|
|
19
|
+
* arriving here at all means a token was consumed. Dispatches through
|
|
20
|
+
* writeWithEnvelope() (LMCP-J2) — idempotency key, sent-log dedup,
|
|
21
|
+
* policy/approval gate, retry, audit — which is the only caller of
|
|
22
|
+
* adapter.write(). This module never calls adapter.write() directly.
|
|
23
|
+
*
|
|
24
|
+
* E4 respect: when args.specialist_id is present (an embedded-specialist
|
|
25
|
+
* caller), the proposed provider+writeKind is checked against that
|
|
26
|
+
* specialist's embedBindings grant via AuthorityGuard before either mode
|
|
27
|
+
* proceeds. A specialist outside its grant is denied here, before the
|
|
28
|
+
* adapter is even resolved — independent of and in addition to the
|
|
29
|
+
* destructive-gate token check server.mjs already performed.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { writeWithEnvelope } from '../../writes/envelope.mjs';
|
|
33
|
+
import { WriteSentLog } from '../../writes/sent-log.mjs';
|
|
34
|
+
import { AuthorityGuard } from '../../embed/authority-guard.mjs';
|
|
35
|
+
import { mergedEmbedBindings } from '../../embed/capability-jobs.mjs';
|
|
36
|
+
import { createGovernedJiraProvider } from '../../providers/contract/adapters/jira/governed-write.mjs';
|
|
37
|
+
import { createJiraTransport } from '../../providers/contract/adapters/jira/transport.mjs';
|
|
38
|
+
import { createGovernedConfluenceProvider } from '../../providers/contract/adapters/confluence/governed-write.mjs';
|
|
39
|
+
import { createConfluenceTransport } from '../../providers/contract/adapters/confluence/transport.mjs';
|
|
40
|
+
import { createGovernedGithubProvider } from '../../providers/contract/adapters/github/governed-write.mjs';
|
|
41
|
+
import githubAdapter from '../../providers/contract/adapters/github/index.mjs';
|
|
42
|
+
|
|
43
|
+
// Real transports read credentials from process.env at construction time
|
|
44
|
+
// (JIRA_URL/JIRA_EMAIL/JIRA_TOKEN, CONFLUENCE_URL/CONFLUENCE_EMAIL/
|
|
45
|
+
// CONFLUENCE_TOKEN); github's transport is the gh CLI, already
|
|
46
|
+
// credential-free at construction. Building the transport lazily per call
|
|
47
|
+
// (rather than once at module load) keeps a missing-credential AuthError
|
|
48
|
+
// scoped to the one call that needed it.
|
|
49
|
+
|
|
50
|
+
const ADAPTER_FACTORIES = {
|
|
51
|
+
jira: () => createGovernedJiraProvider({ jiraTransport: createJiraTransport() }),
|
|
52
|
+
confluence: () => createGovernedConfluenceProvider({ confluenceTransport: createConfluenceTransport() }),
|
|
53
|
+
github: () => createGovernedGithubProvider({ ghAdapter: githubAdapter }),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Resolve the governed-write adapter for a provider name. Isolated as its
|
|
58
|
+
* own function so tests can override resolution via the `deps.resolveAdapter`
|
|
59
|
+
* injection point without touching this module's real-transport wiring.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} provider
|
|
62
|
+
* @returns {{ meta: object, write: Function, search?: Function, renderDryRun?: Function }}
|
|
63
|
+
*/
|
|
64
|
+
function resolveAdapter(provider) {
|
|
65
|
+
const factory = ADAPTER_FACTORIES[provider];
|
|
66
|
+
if (!factory) {
|
|
67
|
+
const known = Object.keys(ADAPTER_FACTORIES).join(', ');
|
|
68
|
+
throw new Error(`provider_write: unknown provider "${provider}" (known: ${known})`);
|
|
69
|
+
}
|
|
70
|
+
return factory();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Check an embedded-specialist caller's proposal against its embedBindings
|
|
75
|
+
* grant. Returns null (no opinion) when args carries no specialist_id, so
|
|
76
|
+
* non-embed callers are unaffected — additive enforcement layered in front
|
|
77
|
+
* of the destructive gate, mirroring lib/embed/capability-jobs.mjs's
|
|
78
|
+
* checkProposalAuthority for the daemon path.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} args
|
|
81
|
+
* @param {{ rootDir?: string, env?: object, embedBindings?: object }} opts
|
|
82
|
+
* @returns {Promise<{ allowed: boolean, reason?: string }|null>}
|
|
83
|
+
*/
|
|
84
|
+
async function checkE4Binding(args, { rootDir, env = process.env, embedBindings } = {}) {
|
|
85
|
+
if (!args.specialist_id) return null;
|
|
86
|
+
|
|
87
|
+
const bindings = embedBindings ?? mergedEmbedBindings({ rootDir, env });
|
|
88
|
+
const guard = new AuthorityGuard({ authority: {} }, null, bindings);
|
|
89
|
+
const result = await guard.check('externalPost', {
|
|
90
|
+
proposal: {
|
|
91
|
+
specialistId: args.specialist_id,
|
|
92
|
+
providerId: args.provider,
|
|
93
|
+
writeKind: args.item?.type,
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// Only a binding-scoped denial (mode: 'denied' with no queueId) short-circuits
|
|
98
|
+
// provider_write; a queued/autonomous authority-level outcome is not this
|
|
99
|
+
// tool's concern — the destructive gate + J2 envelope already own approval.
|
|
100
|
+
if (result.mode === 'denied' && !result.allowed) {
|
|
101
|
+
return { allowed: false, reason: result.reason };
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* provider_write tool implementation.
|
|
108
|
+
*
|
|
109
|
+
* @param {object} args
|
|
110
|
+
* @param {string} args.provider - 'jira' | 'confluence' | 'github'
|
|
111
|
+
* @param {object} args.item - write payload, shape depends on provider (see governed-write.mjs per adapter)
|
|
112
|
+
* @param {boolean} [args.dry_run=true] - render-only when true; never touches adapter.write()
|
|
113
|
+
* @param {string} [args.specialist_id] - embedded-specialist caller id (LMCP-E4 binding check)
|
|
114
|
+
* @param {string} [args.idempotency_key] - explicit idempotency key forwarded to the J2 envelope
|
|
115
|
+
* @param {object} [deps] - injection points for tests
|
|
116
|
+
* @param {Function} [deps.resolveAdapter] - override adapter resolution
|
|
117
|
+
* @param {WriteSentLog} [deps.sentLog] - override sent-log instance
|
|
118
|
+
* @param {object} [deps.embedBindings] - override merged embedBindings map
|
|
119
|
+
* @param {string} [deps.rootDir]
|
|
120
|
+
* @param {object} [deps.env]
|
|
121
|
+
* @returns {Promise<object>}
|
|
122
|
+
*/
|
|
123
|
+
export async function providerWrite(args = {}, deps = {}) {
|
|
124
|
+
const provider = args.provider;
|
|
125
|
+
const item = args.item;
|
|
126
|
+
const dryRun = args.dry_run !== false;
|
|
127
|
+
|
|
128
|
+
if (!provider) return { error: 'provider_write: args.provider is required' };
|
|
129
|
+
if (!item || typeof item !== 'object') return { error: 'provider_write: args.item is required' };
|
|
130
|
+
|
|
131
|
+
const bindingDenial = await checkE4Binding(args, {
|
|
132
|
+
rootDir: deps.rootDir,
|
|
133
|
+
env: deps.env,
|
|
134
|
+
embedBindings: deps.embedBindings,
|
|
135
|
+
});
|
|
136
|
+
if (bindingDenial) {
|
|
137
|
+
return { status: 'denied', provider, dryRun, reason: bindingDenial.reason };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const resolve = deps.resolveAdapter ?? resolveAdapter;
|
|
141
|
+
let adapter;
|
|
142
|
+
try {
|
|
143
|
+
adapter = resolve(provider);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
return { error: err.message ?? String(err) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (dryRun) {
|
|
149
|
+
if (typeof adapter.renderDryRun !== 'function') {
|
|
150
|
+
return {
|
|
151
|
+
status: 'dry-run',
|
|
152
|
+
provider,
|
|
153
|
+
dryRun: true,
|
|
154
|
+
diff: null,
|
|
155
|
+
note: `provider "${provider}" adapter has no renderDryRun; payload echoed verbatim, no validation performed.`,
|
|
156
|
+
payload: item,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const diff = adapter.renderDryRun(item);
|
|
161
|
+
return { status: 'dry-run', provider, dryRun: true, diff };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return { status: 'dry-run-invalid', provider, dryRun: true, error: err.message ?? String(err) };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const sentLog = deps.sentLog ?? new WriteSentLog({ persistPath: WriteSentLog.resolvePersistPath(deps.rootDir ?? process.cwd()) });
|
|
168
|
+
|
|
169
|
+
const envelopeResult = await writeWithEnvelope({
|
|
170
|
+
provider: adapter,
|
|
171
|
+
config: {},
|
|
172
|
+
payload: item,
|
|
173
|
+
dryRun: false,
|
|
174
|
+
sentLog,
|
|
175
|
+
idempotencyKey: args.idempotency_key,
|
|
176
|
+
requestedBy: args.specialist_id ? { role: `cx-${args.specialist_id}` } : {},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
status: envelopeResult.status,
|
|
181
|
+
provider,
|
|
182
|
+
dryRun: false,
|
|
183
|
+
envelope: envelopeResult.envelope,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export default providerWrite;
|
package/lib/mcp/tools/scope.mjs
CHANGED
|
@@ -201,9 +201,6 @@ export function scopeCreate(args = {}) {
|
|
|
201
201
|
// Mutating, gated, destructive: archive a curated scope.
|
|
202
202
|
|
|
203
203
|
export function scopeArchive(args = {}) {
|
|
204
|
-
if (args.confirm !== true) {
|
|
205
|
-
return { error: 'scope_archive requires confirm=true (destructive: moves files into archive/scopes/)' };
|
|
206
|
-
}
|
|
207
204
|
if (!args.id || !args.reason || String(args.reason).trim().length < 8) {
|
|
208
205
|
return { error: 'scope_archive requires id:string and reason:string (>=8 chars)' };
|
|
209
206
|
}
|
package/lib/mcp/tools/skills.mjs
CHANGED
|
@@ -253,7 +253,7 @@ export async function orchestrationPolicy(args) {
|
|
|
253
253
|
const { emitTraceEvent, newTraceId } = await import('../../worker/trace.mjs');
|
|
254
254
|
const cwd = process.cwd();
|
|
255
255
|
const queue = createIntakeQueue(cwd, process.env);
|
|
256
|
-
const intake = queue.read(intakeId);
|
|
256
|
+
const intake = await queue.read(intakeId);
|
|
257
257
|
if (!intake) {
|
|
258
258
|
intakeError = `intake packet not found: ${intakeId}`;
|
|
259
259
|
} else if (!intake.triage) {
|
|
@@ -8,8 +8,6 @@
|
|
|
8
8
|
import { resolve } from 'node:path';
|
|
9
9
|
import { deleteIngestedArtifacts, getStorageStatus, inferProjectName, purgeExpiredData, resetStorage } from '../../storage/admin.mjs';
|
|
10
10
|
import { syncFileStateToSql } from '../../storage/sync.mjs';
|
|
11
|
-
import { consumeApprovalToken } from '../destructive-approval.mjs';
|
|
12
|
-
|
|
13
11
|
export async function storageStatus(args) {
|
|
14
12
|
const cwd = args.cwd ? resolve(String(args.cwd)) : process.cwd();
|
|
15
13
|
const project = args.project ? String(args.project) : inferProjectName(cwd);
|
|
@@ -36,9 +34,6 @@ export async function storageReset(args) {
|
|
|
36
34
|
if (args.confirm !== true) {
|
|
37
35
|
return { error: 'storage_reset requires confirm=true' };
|
|
38
36
|
}
|
|
39
|
-
if (!consumeApprovalToken('storage_reset', args.approval_token)) {
|
|
40
|
-
return { error: 'storage_reset requires a valid out-of-band approval token; confirm=true alone is not authorization' };
|
|
41
|
-
}
|
|
42
37
|
return resetStorage(cwd, {
|
|
43
38
|
env: process.env,
|
|
44
39
|
project,
|
|
@@ -53,9 +48,6 @@ export async function deleteIngestedArtifactsTool(args) {
|
|
|
53
48
|
if (args.confirm !== true) {
|
|
54
49
|
return { error: 'delete_ingested_artifacts requires confirm=true' };
|
|
55
50
|
}
|
|
56
|
-
if (!consumeApprovalToken('delete_ingested_artifacts', args.approval_token)) {
|
|
57
|
-
return { error: 'delete_ingested_artifacts requires a valid out-of-band approval token; confirm=true alone is not authorization' };
|
|
58
|
-
}
|
|
59
51
|
const files = Array.isArray(args.files)
|
|
60
52
|
? args.files.map((value) => String(value))
|
|
61
53
|
: [];
|