@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
|
@@ -77,11 +77,17 @@ try {
|
|
|
77
77
|
const cxDir = doctorRoot();
|
|
78
78
|
mkdirSync(cxDir, { recursive: true });
|
|
79
79
|
|
|
80
|
-
// Update last-agent files for coordination (shared + per-agent)
|
|
81
|
-
|
|
80
|
+
// Update last-agent files for coordination (shared + per-agent). agentId is
|
|
81
|
+
// best-effort (null on hosts that don't populate it on this event) — it lets
|
|
82
|
+
// guard-bash's role-fence check prove that a LATER command's own agent_id
|
|
83
|
+
// is the same subagent this dispatch was for, rather than fencing anyone
|
|
84
|
+
// riding a fresh timestamp (construct-7164).
|
|
85
|
+
const agentEntry = { agent: agentName, agentId: input?.agent_id ?? null, coordination: 'tracker-plus-plan', ts: new Date().toISOString(), outcome, description: description.slice(0, 200) };
|
|
82
86
|
writeFileSync(join(cxDir, 'last-agent.json'), JSON.stringify(agentEntry));
|
|
83
|
-
// Per-agent file prevents one agent dispatch from resetting another's fence
|
|
84
|
-
|
|
87
|
+
// Per-agent file prevents one agent dispatch from resetting another's fence
|
|
88
|
+
// window. Keyed by persona id with the cx- prefix stripped, matching
|
|
89
|
+
// guard-bash's CONSTRUCT_AGENT_ID lookup (construct-diq1).
|
|
90
|
+
const safeName = agentName.replace(/^cx-/, '').replace(/[^a-z0-9._-]/gi, '_');
|
|
85
91
|
writeFileSync(join(cxDir, `last-agent-${safeName}.json`), JSON.stringify(agentEntry));
|
|
86
92
|
|
|
87
93
|
// Per-project agent log (was ~/.cx/agent-log.jsonl which mixed every
|
package/lib/hooks/edit-guard.mjs
CHANGED
|
@@ -41,8 +41,8 @@ const cwd = input?.cwd || process.cwd();
|
|
|
41
41
|
function absOf(p) { return p.startsWith('/') ? p : resolve(cwd, p); }
|
|
42
42
|
|
|
43
43
|
// Role-fence check: if the most recently dispatched persona is one we manage
|
|
44
|
-
// (cx-
|
|
45
|
-
//
|
|
44
|
+
// (cx-operations, cx-qa, cx-security) and the dispatch is fresh, emit an
|
|
45
|
+
// advisory stderr warning when the edit target is outside the fence.
|
|
46
46
|
// Hard-block only when the fence returns "outside-fence" (default-deny case);
|
|
47
47
|
// "needs-approval" routes to existing commit-approval flow.
|
|
48
48
|
|
|
@@ -54,7 +54,7 @@ if ((toolName === 'Edit' || toolName === 'Write' || toolName === 'MultiEdit') &&
|
|
|
54
54
|
const lastTs = last?.ts ? Date.parse(last.ts) : 0;
|
|
55
55
|
const fresh = lastTs && (Date.now() - lastTs) < 10 * 60 * 1000;
|
|
56
56
|
const id = String(last?.agent || '').replace(/^cx-/, '');
|
|
57
|
-
const ONBOARDED = new Set(['
|
|
57
|
+
const ONBOARDED = new Set(['operations', 'qa', 'security']);
|
|
58
58
|
const targetPath = ti.file_path || ti.path || '';
|
|
59
59
|
if (fresh && ONBOARDED.has(id) && targetPath) {
|
|
60
60
|
const { checkAction } = await import('../roles/fence.mjs');
|
|
@@ -41,13 +41,13 @@ if (!CODE_RE.test(rel)) process.exit(0);
|
|
|
41
41
|
const stale = checkGraphStaleness(cwd);
|
|
42
42
|
if (stale.stale) {
|
|
43
43
|
process.stderr.write(
|
|
44
|
-
`[graph-impact] dependency graph stale (${stale.staleReason}). Run \`construct
|
|
44
|
+
`[graph-impact] dependency graph stale (${stale.staleReason}). Run \`construct matrix build\` before trusting impact output.\n`,
|
|
45
45
|
);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const impact = computeImpact({ rootDir: cwd, changedFiles: [rel] });
|
|
49
49
|
if (!impact.graphPresent) {
|
|
50
|
-
process.stderr.write('[graph-impact] no .cx/graph/ present — run `construct
|
|
50
|
+
process.stderr.write('[graph-impact] no .cx/graph/ present — run `construct matrix build` to enable test-impact advisories.\n');
|
|
51
51
|
process.exit(0);
|
|
52
52
|
}
|
|
53
53
|
|
package/lib/hooks/guard-bash.mjs
CHANGED
|
@@ -57,9 +57,15 @@ const WARN_PATTERNS = [
|
|
|
57
57
|
},
|
|
58
58
|
];
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
// agent_id is Claude Code's own subagent marker: present only when this
|
|
61
|
+
// invocation is running inside a subagent's own tool call, absent for
|
|
62
|
+
// main-thread (orchestrator) calls. It is the actor-identity signal the
|
|
63
|
+
// role-fence check below uses to prove WHO is issuing THIS command, instead
|
|
64
|
+
// of trusting how recently some persona was dispatched elsewhere (construct-7164).
|
|
65
|
+
|
|
66
|
+
async function getInvocation() {
|
|
61
67
|
const fromEnv = process.env.TOOL_INPUT_COMMAND;
|
|
62
|
-
if (fromEnv) return fromEnv;
|
|
68
|
+
if (fromEnv) return { command: fromEnv, agentId: null };
|
|
63
69
|
|
|
64
70
|
return new Promise((resolve) => {
|
|
65
71
|
let data = '';
|
|
@@ -68,16 +74,19 @@ async function getCommand() {
|
|
|
68
74
|
rl.on('close', () => {
|
|
69
75
|
try {
|
|
70
76
|
const parsed = JSON.parse(data);
|
|
71
|
-
resolve(
|
|
77
|
+
resolve({
|
|
78
|
+
command: parsed?.tool_input?.command ?? parsed?.command ?? '',
|
|
79
|
+
agentId: parsed?.agent_id ?? null,
|
|
80
|
+
});
|
|
72
81
|
} catch (err) {
|
|
73
82
|
logHookFailure({ hook: 'guard-bash', err, phase: 'parse' });
|
|
74
|
-
resolve('');
|
|
83
|
+
resolve({ command: '', agentId: null });
|
|
75
84
|
}
|
|
76
85
|
});
|
|
77
86
|
});
|
|
78
87
|
}
|
|
79
88
|
|
|
80
|
-
const command = await
|
|
89
|
+
const { command, agentId: currentAgentId } = await getInvocation();
|
|
81
90
|
|
|
82
91
|
if (!command) process.exit(0);
|
|
83
92
|
|
|
@@ -94,10 +103,21 @@ for (const { pattern, reason } of WARN_PATTERNS) {
|
|
|
94
103
|
}
|
|
95
104
|
}
|
|
96
105
|
|
|
97
|
-
// Role-fence check:
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
106
|
+
// Role-fence check: evaluate the command against a persona's fence, but only
|
|
107
|
+
// when the CURRENT invocation's actor identity is verifiably that persona —
|
|
108
|
+
// recency of a past dispatch is not proof of who is issuing THIS command
|
|
109
|
+
// (construct-7164: an orchestrator's or a different subagent's own bash
|
|
110
|
+
// calls were getting fenced by a persona dispatched minutes earlier
|
|
111
|
+
// elsewhere). Two identity signals are trusted: CONSTRUCT_AGENT_ID, an
|
|
112
|
+
// explicit self-declaration some hosts set before invoking commands as a
|
|
113
|
+
// given persona; and agent_id, Claude Code's own per-subagent marker, which
|
|
114
|
+
// is guaranteed absent for main-thread calls. The shared last-agent.json
|
|
115
|
+
// (no identity attached) can only corroborate a dispatch already proven by
|
|
116
|
+
// agent_id — it is never sufficient on its own. git commit/push are always
|
|
117
|
+
// commit/push actions; bd ... maps to allowedCommands prefix-match;
|
|
118
|
+
// everything else is the bash action.
|
|
119
|
+
|
|
120
|
+
const FENCE_WINDOW_MS = 5 * 60 * 1000;
|
|
101
121
|
|
|
102
122
|
if (process.env.CONSTRUCT_ROLES !== 'off') {
|
|
103
123
|
try {
|
|
@@ -105,24 +125,30 @@ if (process.env.CONSTRUCT_ROLES !== 'off') {
|
|
|
105
125
|
const id = String(process.env.CONSTRUCT_AGENT_ID || '').replace(/^cx-/, '');
|
|
106
126
|
let agentData = null;
|
|
107
127
|
|
|
108
|
-
//
|
|
128
|
+
// Self-declared identity: the per-agent file is keyed by the same id the
|
|
129
|
+
// current invocation is claiming, so this match is identity-comparable
|
|
130
|
+
// by construction.
|
|
109
131
|
if (id) {
|
|
110
132
|
const perAgentPath = join(cxDir, `last-agent-${id.replace(/[^a-z0-9._-]/gi, '_')}.json`);
|
|
111
133
|
if (existsSync(perAgentPath)) {
|
|
112
134
|
agentData = JSON.parse(readFileSync(perAgentPath, 'utf8'));
|
|
113
135
|
}
|
|
114
136
|
}
|
|
115
|
-
//
|
|
116
|
-
|
|
137
|
+
// No self-declared id: only trust the shared dispatch record when the
|
|
138
|
+
// CURRENT invocation's own agent_id matches the one recorded at dispatch
|
|
139
|
+
// time, proving this is the SAME subagent call rather than a different
|
|
140
|
+
// actor riding a fresh timestamp.
|
|
141
|
+
if (!agentData && currentAgentId) {
|
|
117
142
|
const sharedPath = join(cxDir, 'last-agent.json');
|
|
118
143
|
if (existsSync(sharedPath)) {
|
|
119
|
-
|
|
144
|
+
const shared = JSON.parse(readFileSync(sharedPath, 'utf8'));
|
|
145
|
+
if (shared?.agentId && shared.agentId === currentAgentId) agentData = shared;
|
|
120
146
|
}
|
|
121
147
|
}
|
|
122
148
|
|
|
123
149
|
if (agentData) {
|
|
124
150
|
const lastTs = agentData?.ts ? Date.parse(agentData.ts) : 0;
|
|
125
|
-
const fresh = lastTs && (Date.now() - lastTs) <
|
|
151
|
+
const fresh = lastTs && (Date.now() - lastTs) < FENCE_WINDOW_MS;
|
|
126
152
|
const personaId = id || String(agentData?.agent || '').replace(/^cx-/, '');
|
|
127
153
|
if (fresh && personaId) {
|
|
128
154
|
const { isOnboarded } = await import('../roles/manifest.mjs');
|
|
@@ -145,7 +171,7 @@ if (process.env.CONSTRUCT_ROLES !== 'off') {
|
|
|
145
171
|
`[fence] cx-${personaId} cannot run this command — outside declared fence.\n` +
|
|
146
172
|
`Command: ${trimmed.slice(0, 200)}\n` +
|
|
147
173
|
`Allowed commands: ${allowedList}\n` +
|
|
148
|
-
`To bypass: CONSTRUCT_ROLES=off <command
|
|
174
|
+
`To bypass: set CONSTRUCT_ROLES=off as a real environment variable before starting this session (this hook runs as a separate process, so an inline "CONSTRUCT_ROLES=off <command>" prefix on the blocked command never reaches it).\n`
|
|
149
175
|
);
|
|
150
176
|
process.exit(2);
|
|
151
177
|
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* lib/hooks/mcp-health-check.mjs — MCP health check hook — verifies MCP servers are reachable before tool use.
|
|
4
4
|
*
|
|
5
|
-
* Runs as PreToolUse on MCP tool calls. Checks that the target MCP server is running and reachable. Emits a warning (does not block) if the server is unavailable.
|
|
5
|
+
* Runs as PreToolUse on MCP tool calls. Checks that the target MCP server is running and reachable. Emits a warning (does not block) if the server is unavailable. A successful tool use resets the failure count for that server.
|
|
6
6
|
*
|
|
7
7
|
* @p95ms 51
|
|
8
8
|
* @maxBlockingScope none (PreToolUse, warn-only)
|
|
9
9
|
*
|
|
10
10
|
* @lifecycle PreToolUse
|
|
11
11
|
* @matcher mcp__.*
|
|
12
|
-
* @exits 0 = pass | 2 =
|
|
12
|
+
* @exits 0 = pass (health check is warn-only; never blocks) | 2 = blocked by role fence (unrelated to health, see below)
|
|
13
13
|
*/
|
|
14
14
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
15
15
|
import { homedir } from 'os';
|
|
@@ -46,12 +46,19 @@ if (markFailure) {
|
|
|
46
46
|
process.exit(0);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// Warn-only per @maxBlockingScope none: a tool-level failure is not proof the
|
|
50
|
+
// server is down, so this notice never blocks. Successful use (below) resets
|
|
51
|
+
// the counter, so the notice only appears while failures are still recent.
|
|
52
|
+
|
|
49
53
|
const entry = cache[serverSlug];
|
|
50
54
|
if (entry?.status === 'unhealthy' && (Date.now() - entry.since) < CACHE_TTL_MS) {
|
|
51
55
|
process.stderr.write(
|
|
52
|
-
`[mcp-health] The ${serverSlug} connection
|
|
56
|
+
`[mcp-health] The ${serverSlug} connection failed ${entry.failures} time${entry.failures !== 1 ? 's' : ''} recently.\n`
|
|
53
57
|
);
|
|
54
|
-
|
|
58
|
+
} else if (entry) {
|
|
59
|
+
entry.status = 'healthy';
|
|
60
|
+
entry.failures = 0;
|
|
61
|
+
try { writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2)); } catch { /* best effort */ }
|
|
55
62
|
}
|
|
56
63
|
|
|
57
64
|
// Role-fence check for MCP tool calls. Most personas have no MCP fence
|
|
@@ -20,22 +20,41 @@
|
|
|
20
20
|
* @matcher *
|
|
21
21
|
* @exits 0 = pass
|
|
22
22
|
*/
|
|
23
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
24
24
|
import { execFileSync } from 'node:child_process';
|
|
25
25
|
import { homedir } from 'node:os';
|
|
26
26
|
import { join } from 'node:path';
|
|
27
27
|
import {
|
|
28
28
|
applyToEnv,
|
|
29
29
|
classifyProviderFailure,
|
|
30
|
+
defaultModelRegistryPath,
|
|
30
31
|
readOpenRouterApiKeyFromOpenCodeConfig,
|
|
31
32
|
selectFallbackModel,
|
|
32
33
|
writeProviderCooldown,
|
|
33
34
|
} from '../model-router.mjs';
|
|
35
|
+
import { extractOpRef, resolveOpRef, resolveSecret } from '../providers/secret-resolver.mjs';
|
|
34
36
|
import { readHookInput } from './_lib/input.mjs';
|
|
35
37
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
36
38
|
|
|
37
39
|
const cooldownPath = join(doctorRoot(), 'provider-cooldowns.json');
|
|
38
40
|
const envPath = join(process.cwd(), '.env');
|
|
41
|
+
const toolkitDir = process.env.CX_TOOLKIT_DIR || join(homedir(), '.construct');
|
|
42
|
+
|
|
43
|
+
// The fallback candidate chain must be loaded and bound into scope before
|
|
44
|
+
// selectFallbackModel runs, or its tier definition is always empty and a
|
|
45
|
+
// fallback can never fire — same registry file resolveModelTiers reads
|
|
46
|
+
// (specialists/org/models.json under the toolkit dir).
|
|
47
|
+
|
|
48
|
+
function readRegistryModels(env) {
|
|
49
|
+
try {
|
|
50
|
+
const registryPath = defaultModelRegistryPath(env);
|
|
51
|
+
if (!existsSync(registryPath)) return {};
|
|
52
|
+
const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
53
|
+
return registry?.models ?? {};
|
|
54
|
+
} catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
39
58
|
|
|
40
59
|
const input = readHookInput();
|
|
41
60
|
|
|
@@ -45,7 +64,8 @@ if (!classified || !classified.retryable) process.exit(0);
|
|
|
45
64
|
process.stderr.write(`[model-fallback] ${classified.kind} detected (provider: ${classified.provider ?? 'unknown'}).\n`);
|
|
46
65
|
|
|
47
66
|
const now = Date.now();
|
|
48
|
-
const
|
|
67
|
+
const registryModels = readRegistryModels(process.env);
|
|
68
|
+
const result = selectFallbackModel({ hookInput: input, envPath, cooldownPath, registryModels, now });
|
|
49
69
|
|
|
50
70
|
if (result) {
|
|
51
71
|
process.stderr.write(`[model-fallback] Switching ${result.tier} → ${result.targetModel} (reason: ${result.reason}).\n`);
|
|
@@ -55,20 +75,44 @@ if (result) {
|
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
// No candidate resolved — fall back to full `construct models --apply` if possible.
|
|
58
|
-
|
|
78
|
+
// No fallback was applied here, so no cooldown is written: writing one on a
|
|
79
|
+
// no-op path would suppress the provider on the NEXT failure even though this
|
|
80
|
+
// hook run changed nothing (construct-uccl.3).
|
|
59
81
|
const constructBin = join(toolkitDir, 'bin', 'construct');
|
|
60
82
|
|
|
61
83
|
if (!existsSync(constructBin)) {
|
|
62
84
|
process.stderr.write(`[model-fallback] No fallback candidate and construct binary not found at ${constructBin}.\n`);
|
|
63
|
-
if (classified.provider) writeProviderCooldown(cooldownPath, classified.provider, now);
|
|
64
85
|
process.exit(0);
|
|
65
86
|
}
|
|
66
87
|
|
|
67
|
-
|
|
88
|
+
function materializeConfiguredKey(rawValue) {
|
|
89
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
|
|
90
|
+
const ref = extractOpRef(rawValue);
|
|
91
|
+
try {
|
|
92
|
+
return ref ? resolveOpRef(ref) : rawValue;
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function resolveOpenRouterApiKey() {
|
|
99
|
+
try {
|
|
100
|
+
const envKey = resolveSecret('OPENROUTER_API_KEY', { env: process.env });
|
|
101
|
+
if (envKey) return envKey;
|
|
102
|
+
} catch {
|
|
103
|
+
// Fall through to the OpenCode provider config.
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
return materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const openRouterApiKey = resolveOpenRouterApiKey();
|
|
68
113
|
|
|
69
114
|
if (!openRouterApiKey) {
|
|
70
115
|
process.stderr.write('[model-fallback] No fallback candidate and no OpenRouter API key — cannot poll models.\n');
|
|
71
|
-
if (classified.provider) writeProviderCooldown(cooldownPath, classified.provider, now);
|
|
72
116
|
process.exit(0);
|
|
73
117
|
}
|
|
74
118
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* The orchestration_policy tool classifies a request into immediate / focused /
|
|
6
6
|
* orchestrated. An orchestrated verdict means the orchestrator must dispatch the
|
|
7
|
-
* specialist chain (cx-architect, cx-security, cx-
|
|
7
|
+
* specialist chain (cx-architect, cx-security, cx-reviewer, …) before
|
|
8
8
|
* authoring the deliverable — not write it solo. The persona instructs this, but
|
|
9
9
|
* instruction is not enforcement: this hook is the Claude-only barrier.
|
|
10
10
|
*
|
|
@@ -87,10 +87,21 @@ if (event === 'PostToolUse' && isPolicyTool(toolName)) {
|
|
|
87
87
|
process.exit(0);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
// A dispatch tool arms `dispatched:true` unless its own response proves the
|
|
91
|
+
// dispatch produced nothing to synthesize (degraded run, zero tasks, or an
|
|
92
|
+
// error/failed status). Task dispatches carry no envelope — an empty/absent
|
|
93
|
+
// blob matches none of these markers, so they still clear the guard.
|
|
94
|
+
|
|
90
95
|
if (event === 'PostToolUse' && isDispatchTool(toolName)) {
|
|
91
96
|
if (!hasCx(cwd)) process.exit(0);
|
|
92
97
|
const state = readState(cwd);
|
|
93
|
-
if (state?.pending)
|
|
98
|
+
if (state?.pending) {
|
|
99
|
+
const blob = JSON.stringify(input?.tool_response ?? '').replace(/\\/g, '');
|
|
100
|
+
const failed = /"degraded"\s*:\s*true/.test(blob)
|
|
101
|
+
|| /"tasks"\s*:\s*\[\s*\]/.test(blob)
|
|
102
|
+
|| /"status"\s*:\s*"(error|failed)"/.test(blob);
|
|
103
|
+
if (!failed) writeState(cwd, { ...state, dispatched: true });
|
|
104
|
+
}
|
|
94
105
|
process.exit(0);
|
|
95
106
|
}
|
|
96
107
|
|
|
@@ -117,7 +128,7 @@ if (event === 'PreToolUse' && /^(Write|Edit|MultiEdit)$/.test(toolName)) {
|
|
|
117
128
|
+ 'but no specialist dispatch (orchestration_run / Task) has run yet.\n'
|
|
118
129
|
+ 'You may not author the deliverable solo. Emit the task-packet and dispatch the chain '
|
|
119
130
|
+ '(e.g. cx-architect owns structure, cx-security challenges the credential/state model, '
|
|
120
|
-
+ 'cx-
|
|
131
|
+
+ 'cx-reviewer pressure-tests the approach), then synthesize their verdicts in Construct\'s voice.\n'
|
|
121
132
|
+ 'If this write IS post-dispatch synthesis, run the dispatch tool first so the guard clears.\n',
|
|
122
133
|
);
|
|
123
134
|
process.exit(2);
|