@geraldmaron/construct 1.4.1 → 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 +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- 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 +7 -30
- 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 +109 -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 +23 -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/embed/worker.mjs +5 -0
- 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 +60 -11
- package/lib/export-validate.mjs +34 -2
- 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/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- 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/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- 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-approval.mjs +57 -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 +185 -1268
- 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-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- 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 +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- 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 +53 -20
- 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/daemon-entry.mjs +6 -0
- 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 +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- 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/path-policy.mjs +56 -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 +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- 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/catalog.mjs +6 -0
- 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 +15 -4
- 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/registry/validate.mjs +3 -3
- 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/sandbox.mjs +1 -1
- 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 +93 -24
- 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 +84 -7
- 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 +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- 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 +41 -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 +27 -31
- 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/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- 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
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
* Storage layout:
|
|
10
10
|
* .cx/observations/index.json — lightweight listing for fast filtering
|
|
11
11
|
* .cx/observations/<id>.json — full observation record
|
|
12
|
+
* <stateRoot>/lancedb — vector index (ADR-0066: machine-scoped,
|
|
13
|
+
* not project-local); the JSON records
|
|
14
|
+
* above remain project-local for now
|
|
12
15
|
*/
|
|
13
16
|
import crypto from 'node:crypto';
|
|
14
17
|
import fs from 'node:fs';
|
|
@@ -16,6 +19,7 @@ import path from 'node:path';
|
|
|
16
19
|
import { embedText as embedTextEngine } from './storage/embeddings-engine.mjs';
|
|
17
20
|
import { VectorClient } from './storage/vector-client.mjs';
|
|
18
21
|
import { ensureCxDir } from './project-init-shared.mjs';
|
|
22
|
+
import { resolveStateDir } from './state-root.mjs';
|
|
19
23
|
|
|
20
24
|
const OBS_DIR = '.cx/observations';
|
|
21
25
|
const INDEX_FILE = 'index.json';
|
|
@@ -31,7 +35,7 @@ const MAX_INDEX = 1000;
|
|
|
31
35
|
function vectorClientFor(rootDir) {
|
|
32
36
|
const env = process.env.CONSTRUCT_LANCEDB_PATH
|
|
33
37
|
? process.env
|
|
34
|
-
: { ...process.env, CONSTRUCT_LANCEDB_PATH:
|
|
38
|
+
: { ...process.env, CONSTRUCT_LANCEDB_PATH: resolveStateDir(rootDir, 'lancedb', { ensureDir: false }) };
|
|
35
39
|
return new VectorClient({ env });
|
|
36
40
|
}
|
|
37
41
|
|
|
@@ -111,7 +115,7 @@ function logCapDrop(rootDir, kind, dropped, total) {
|
|
|
111
115
|
dropped,
|
|
112
116
|
total,
|
|
113
117
|
cap: MAX_INDEX,
|
|
114
|
-
remedy: '
|
|
118
|
+
remedy: "Run 'construct memory consolidate' to compact stored observations.",
|
|
115
119
|
});
|
|
116
120
|
fs.appendFileSync(logPath, entry + '\n');
|
|
117
121
|
} catch { /* cap warnings are best-effort */ }
|
|
@@ -18,6 +18,7 @@ import { spawnSync } from "node:child_process";
|
|
|
18
18
|
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
19
19
|
import { tmpdir } from "node:os";
|
|
20
20
|
import { join } from "node:path";
|
|
21
|
+
import { isMainModule } from '../roots.mjs';
|
|
21
22
|
|
|
22
23
|
const DEFAULT_NUM_CTX = 32768;
|
|
23
24
|
const CHATML_STOPS = ["<|im_start|>", "<|im_end|>", "<|endoftext|>"];
|
|
@@ -325,7 +326,7 @@ export async function probeStreamingToolCallLeak(model, { baseURL = "http://127.
|
|
|
325
326
|
}
|
|
326
327
|
}
|
|
327
328
|
|
|
328
|
-
if (import.meta.url
|
|
329
|
+
if (isMainModule(import.meta.url)) {
|
|
329
330
|
if (process.argv.includes("--probe")) {
|
|
330
331
|
const model = process.argv.find((a) => a.startsWith("--model="))?.split("=")[1];
|
|
331
332
|
const targets = model ? [model] : listModels();
|
package/lib/opencode-config.mjs
CHANGED
|
@@ -175,10 +175,20 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
|
|
|
175
175
|
if (sessions.includes('github-copilot')) {
|
|
176
176
|
const envValues = parseEnvFile(getUserEnvPath());
|
|
177
177
|
const port = envValues.COPILOT_BRIDGE_PORT || '5174';
|
|
178
|
-
|
|
178
|
+
const provider = config.provider['github-copilot'] || {
|
|
179
179
|
baseUrl: `http://localhost:${port}/v1`,
|
|
180
|
-
apiKey: "noop" //
|
|
180
|
+
apiKey: "noop" // Copilot itself is authed via gh CLI inside the bridge; this field is unused
|
|
181
181
|
};
|
|
182
|
+
|
|
183
|
+
// The bridge now requires a per-launch bearer token. Wire it as an {env:} reference
|
|
184
|
+
// so OpenCode resolves it from its own env at request time and the token is never
|
|
185
|
+
// written into opencode.json in the clear.
|
|
186
|
+
if (envValues.CONSTRUCT_COPILOT_BRIDGE_TOKEN) {
|
|
187
|
+
provider.options = provider.options || {};
|
|
188
|
+
provider.options.headers = provider.options.headers || {};
|
|
189
|
+
provider.options.headers.Authorization = '{env:CONSTRUCT_COPILOT_BRIDGE_TOKEN}';
|
|
190
|
+
}
|
|
191
|
+
config.provider['github-copilot'] = provider;
|
|
182
192
|
}
|
|
183
193
|
}
|
|
184
194
|
|
|
@@ -259,6 +269,15 @@ export function writeOpenCodeConfig(config, file = findOpenCodeConfigPath()) {
|
|
|
259
269
|
});
|
|
260
270
|
}
|
|
261
271
|
|
|
262
|
-
fs.writeFileSync(target, `${JSON.stringify(sanitizeOpenCodeConfig(config), null, 2)}\n`, "utf8");
|
|
272
|
+
fs.writeFileSync(target, `${JSON.stringify(sanitizeOpenCodeConfig(config), null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
273
|
+
|
|
274
|
+
// opencode.json can carry a resolved provider apiKey or Authorization header.
|
|
275
|
+
// writeFileSync keeps an existing inode's mode, so the mode option above does not
|
|
276
|
+
// tighten a file an earlier write left 0644; re-apply 0600 on every write, matching
|
|
277
|
+
// lib/env-config.mjs's writeEnvValues contract for config.env.
|
|
278
|
+
|
|
279
|
+
if (process.platform !== "win32") {
|
|
280
|
+
try { fs.chmodSync(target, 0o600); } catch { /* exotic filesystem */ }
|
|
281
|
+
}
|
|
263
282
|
return target;
|
|
264
283
|
}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Intercepts OpenCode session events (session.created, session.idle, session.error),
|
|
5
5
|
* records each as a telemetry trace, and applies model fallback on rate-limit errors.
|
|
6
|
+
* Also flags model-attempted calls to tools OpenCode rejects as unavailable
|
|
7
|
+
* (construct-2q2m) into the shared tool-failures observation log.
|
|
6
8
|
*/
|
|
7
9
|
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
8
10
|
import { loadRegistry } from './registry/loader.mjs';
|
|
@@ -11,6 +13,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
11
13
|
import { spawnSync } from "node:child_process";
|
|
12
14
|
import { randomUUID } from "node:crypto";
|
|
13
15
|
import { findOpenCodeConfigPath } from "./opencode-config.mjs";
|
|
16
|
+
import { isPlaceholder } from "./providers/credential-sources.mjs";
|
|
14
17
|
import { classifyProviderFailure, readCurrentModels, resolveExecutionContractModelMetadata, resolveFallbackAction, selectModelTierForWorkCategory } from "./model-router.mjs";
|
|
15
18
|
import { resolveRuntimePromptMetadata } from "./prompt-composer.js";
|
|
16
19
|
import { enrichMetadataWithPrompt } from "./prompt-metadata.mjs";
|
|
@@ -30,6 +33,7 @@ import { syncModelPricing, refreshPricingCatalog } from "./telemetry/model-prici
|
|
|
30
33
|
import { estimateUsageCost } from "./telemetry/model-pricing-catalog.mjs";
|
|
31
34
|
import { buildSessionPrelude } from "./intake/session-prelude.mjs";
|
|
32
35
|
import { doctorRoot } from './config/xdg.mjs';
|
|
36
|
+
import { recordToolFailure } from "./mcp/tool-recovery.mjs";
|
|
33
37
|
|
|
34
38
|
const EFFICIENCY_SESSION_IDLE_RESET_MS = 2 * 60 * 60 * 1000;
|
|
35
39
|
const EFFICIENCY_REPEATED_READ_THRESHOLD = 5;
|
|
@@ -59,6 +63,119 @@ const RATE_LIMIT_PATTERNS = [
|
|
|
59
63
|
/no such model/i,
|
|
60
64
|
];
|
|
61
65
|
|
|
66
|
+
// construct-2q2m: OpenCode's own tool resolution (packages/opencode/src/session/llm/request.ts,
|
|
67
|
+
// resolveTools) filters disabled/denied tools out of the schema before the model ever sees them,
|
|
68
|
+
// so a model-hallucinated call to a tool name outside that resolved set has no single documented
|
|
69
|
+
// error shape in this repo's OpenCode source audit (docs/notes/research/2026-06-construct-audit/
|
|
70
|
+
// 20-opencode-ecosystem.md) — it is an AI-SDK/provider-level rejection, not an OpenCode permission
|
|
71
|
+
// deny (which instead flows through permission.ask and stays in-schema). These patterns match the
|
|
72
|
+
// Vercel AI SDK's NoSuchToolError phrasing plus common "tool not found" provider error text, so at
|
|
73
|
+
// least one branch catches whichever shape OpenCode actually surfaces. Unverified against a live
|
|
74
|
+
// OpenCode session — see detectUnavailableToolRejections doc comment.
|
|
75
|
+
const UNAVAILABLE_TOOL_PATTERNS = [
|
|
76
|
+
/\bno such tool\b/i,
|
|
77
|
+
/\bunknown tool\b/i,
|
|
78
|
+
/\bunavailable tool\b/i,
|
|
79
|
+
/\btool\b[^.\n]{0,60}\b(?:not found|not available|not registered|not permitted|does not exist)\b/i,
|
|
80
|
+
/\bNoSuchToolError\b/i,
|
|
81
|
+
/is not a (?:known|recognized|valid|registered) tool/i,
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
function textLooksLikeUnavailableTool(text) {
|
|
85
|
+
return typeof text === "string" && text.length > 0 && UNAVAILABLE_TOOL_PATTERNS.some((pattern) => pattern.test(text));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toolNameFromPart(part) {
|
|
89
|
+
if (!part || typeof part !== "object") return null;
|
|
90
|
+
const typ = typeof part.type === "string" ? part.type.toLowerCase() : "";
|
|
91
|
+
if (typ === "tool" || typ === "tool-call" || typ === "tool_call") {
|
|
92
|
+
return stripPlaceholder(part.tool || part.toolName || part.name) || null;
|
|
93
|
+
}
|
|
94
|
+
if (typ === "tool-invocation" || typ === "tool_invocation") {
|
|
95
|
+
const invocation = part.toolInvocation || part;
|
|
96
|
+
return stripPlaceholder(invocation.toolName || invocation.tool || invocation.name) || null;
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toolPartErrorText(part) {
|
|
102
|
+
if (!part || typeof part !== "object") return "";
|
|
103
|
+
const typ = typeof part.type === "string" ? part.type.toLowerCase() : "";
|
|
104
|
+
if (typ === "tool" || typ === "tool-call" || typ === "tool_call") {
|
|
105
|
+
return flatten(part.state?.error ?? part.state?.message ?? "");
|
|
106
|
+
}
|
|
107
|
+
if (typ === "tool-invocation" || typ === "tool_invocation") {
|
|
108
|
+
const invocation = part.toolInvocation || part;
|
|
109
|
+
return flatten(invocation.error ?? invocation.state?.error ?? "");
|
|
110
|
+
}
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function toolPartIsErrorState(part) {
|
|
115
|
+
const typ = typeof part?.type === "string" ? part.type.toLowerCase() : "";
|
|
116
|
+
if (typ === "tool" || typ === "tool-call" || typ === "tool_call") {
|
|
117
|
+
const status = part.state?.status ?? part.state;
|
|
118
|
+
return status === "error";
|
|
119
|
+
}
|
|
120
|
+
if (typ === "tool-invocation" || typ === "tool_invocation") {
|
|
121
|
+
const invocation = part.toolInvocation || part;
|
|
122
|
+
return invocation?.state === "error" || invocation?.status === "error";
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Detects a model-attempted call to a tool OpenCode rejects as unavailable, from
|
|
128
|
+
// either a session.error event or an error-state tool part inside message.updated.
|
|
129
|
+
// Returns one hit per match; each hit best-effort resolves the tool name (null when
|
|
130
|
+
// the raw text does not identify it structurally). Hits append to the shared
|
|
131
|
+
// tool-failures observation log (lib/mcp/tool-recovery.mjs) so both this host-side
|
|
132
|
+
// signal and construct-mcp's own server-side tool-name-miss capture feed the same
|
|
133
|
+
// learning-loop aggregation (summarizeToolFailures).
|
|
134
|
+
//
|
|
135
|
+
// Pattern-matched against documented AI-SDK/provider error phrasing rather than a
|
|
136
|
+
// live OpenCode session; construct-2q2m's open item is confirming the real event
|
|
137
|
+
// shape and, if it differs, adjusting the patterns/field lookups above.
|
|
138
|
+
|
|
139
|
+
export function detectUnavailableToolRejections(event) {
|
|
140
|
+
if (event?.type === "session.error") {
|
|
141
|
+
const error = event?.error && typeof event.error === "object" ? event.error : {};
|
|
142
|
+
const text = flatten([error.message, error.name, error.code, event?.message]);
|
|
143
|
+
if (!textLooksLikeUnavailableTool(text)) return [];
|
|
144
|
+
return [{
|
|
145
|
+
tool: stripPlaceholder(error.tool || error.toolName) || null,
|
|
146
|
+
source: "session.error",
|
|
147
|
+
text: text.slice(0, 500),
|
|
148
|
+
}];
|
|
149
|
+
}
|
|
150
|
+
if (event?.type === "message.updated") {
|
|
151
|
+
const info = event?.properties?.info;
|
|
152
|
+
const parts = Array.isArray(info?.parts) ? info.parts : [];
|
|
153
|
+
const hits = [];
|
|
154
|
+
for (const part of parts) {
|
|
155
|
+
if (!toolPartIsErrorState(part)) continue;
|
|
156
|
+
const text = toolPartErrorText(part);
|
|
157
|
+
if (!textLooksLikeUnavailableTool(text)) continue;
|
|
158
|
+
hits.push({ tool: toolNameFromPart(part), source: "message.part", text: text.slice(0, 500) });
|
|
159
|
+
}
|
|
160
|
+
return hits;
|
|
161
|
+
}
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function recordUnavailableToolRejections(event, { toolkitDir = process.env.CX_TOOLKIT_DIR, env = process.env } = {}) {
|
|
166
|
+
const hits = detectUnavailableToolRejections(event);
|
|
167
|
+
if (!hits.length) return hits;
|
|
168
|
+
const rootDir = toolkitDir || env.CX_TOOLKIT_DIR || process.cwd();
|
|
169
|
+
for (const hit of hits) {
|
|
170
|
+
recordToolFailure(rootDir, {
|
|
171
|
+
tool: hit.tool || "(unknown)",
|
|
172
|
+
code: "opencode_tool_unavailable",
|
|
173
|
+
message: hit.text,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return hits;
|
|
177
|
+
}
|
|
178
|
+
|
|
62
179
|
const RUNTIME_TRACE_EVENTS = new Set([
|
|
63
180
|
"session.created",
|
|
64
181
|
"session.idle",
|
|
@@ -787,14 +904,14 @@ function writeState(payload, env = process.env) {
|
|
|
787
904
|
writeFileSync(statePath, JSON.stringify(payload, null, 2));
|
|
788
905
|
}
|
|
789
906
|
|
|
790
|
-
function readOpenRouterApiKey(configPath = findOpenCodeConfigPath(), env = process.env) {
|
|
907
|
+
export function readOpenRouterApiKey(configPath = findOpenCodeConfigPath(), env = process.env) {
|
|
791
908
|
if (env.OPENROUTER_API_KEY) return env.OPENROUTER_API_KEY;
|
|
792
909
|
try {
|
|
793
910
|
const config = JSON.parse(readFileSync(configPath, "utf8"));
|
|
794
911
|
const auth = config?.provider?.openrouter?.options?.headers?.Authorization;
|
|
795
912
|
if (typeof auth !== "string") return "";
|
|
796
913
|
const key = auth.replace(/^Bearer\s+/i, "").trim();
|
|
797
|
-
if (
|
|
914
|
+
if (isPlaceholder(key)) return "";
|
|
798
915
|
return key;
|
|
799
916
|
} catch {
|
|
800
917
|
return "";
|
|
@@ -916,6 +1033,7 @@ export function createConstructOpenCodePlugin({
|
|
|
916
1033
|
await safe(() => emitRuntimeTrace(event, { client, env }), "legacy-trace", client);
|
|
917
1034
|
await safe(() => emitSessionPrelude(event, { client, env, cwd: toolkitDir }), "session-prelude", client);
|
|
918
1035
|
await maybeApplyModelFallback(event, { client, env, toolkitDir, configPath });
|
|
1036
|
+
await safe(() => recordUnavailableToolRejections(event, { toolkitDir, env }), "tool-miss-capture", client);
|
|
919
1037
|
try {
|
|
920
1038
|
const { warnings } = trackReadEfficiencyFromMessage(event, { env, cwd: toolkitDir || process.cwd() });
|
|
921
1039
|
for (const message of warnings) {
|
package/lib/oracle/actions.mjs
CHANGED
|
@@ -5,10 +5,16 @@
|
|
|
5
5
|
* sync (Construct tool repo only). Approve actions queue to
|
|
6
6
|
* <project>/.cx/oracle/pending.jsonl. High-severity gaps auto-raise beads
|
|
7
7
|
* when enabled. Denied actions are skipped.
|
|
8
|
+
*
|
|
9
|
+
* triagePending() flushes an accumulated pending.jsonl backlog to one
|
|
10
|
+
* representative row per dedupKey (pending or already-expired; approved
|
|
11
|
+
* rows are left untouched), archiving every superseded row to
|
|
12
|
+
* pending-archive.jsonl so the original set stays recoverable.
|
|
8
13
|
*/
|
|
9
14
|
|
|
10
15
|
import fs from 'node:fs';
|
|
11
16
|
import path from 'node:path';
|
|
17
|
+
import crypto from 'node:crypto';
|
|
12
18
|
import { spawnSync } from 'node:child_process';
|
|
13
19
|
import { fileURLToPath } from 'node:url';
|
|
14
20
|
|
|
@@ -27,19 +33,59 @@ function pendingPath(projectDir) {
|
|
|
27
33
|
return path.join(projectDir, '.cx', 'oracle', 'pending.jsonl');
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
function pendingArchivePath(projectDir) {
|
|
37
|
+
return path.join(projectDir, '.cx', 'oracle', 'pending-archive.jsonl');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Recurring machine-generated decisions (re-run tests / review contract
|
|
41
|
+
// violations / rebuild the dependency matrix) are symptoms of noisy
|
|
42
|
+
// producers, not decisions in their own right — approving the survivor
|
|
43
|
+
// clears the queue but does not fix the producer. Surfacing the linkage on
|
|
44
|
+
// the survivor keeps that distinction visible to whoever reviews the triage
|
|
45
|
+
// output.
|
|
46
|
+
|
|
47
|
+
const RECURRING_PRODUCER_LINKAGE = [
|
|
48
|
+
{ pattern: /re-run affected tests/i, linkedTo: ['construct-r8wr.6'] },
|
|
49
|
+
{ pattern: /review recent contract violations/i, linkedTo: ['construct-r8wr.1'] },
|
|
50
|
+
{ pattern: /rebuild the dependency matrix/i, linkedTo: ['construct-r8wr.6'] },
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
function producerLinkageFor(summary) {
|
|
54
|
+
const match = RECURRING_PRODUCER_LINKAGE.find((entry) => entry.pattern.test(summary || ''));
|
|
55
|
+
return match ? match.linkedTo : undefined;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ORACLE_PENDING_MAX = 50;
|
|
59
|
+
const ORACLE_PENDING_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
60
|
+
|
|
30
61
|
function ensureOracleDir(projectDir) {
|
|
31
62
|
const dir = path.join(projectDir, '.cx', 'oracle');
|
|
32
63
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
33
64
|
return dir;
|
|
34
65
|
}
|
|
35
66
|
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
67
|
+
function sha256(input) {
|
|
68
|
+
return crypto.createHash('sha256').update(input).digest('hex');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function normalizeSummary(summary) {
|
|
72
|
+
return String(summary || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function pendingTarget(record = {}) {
|
|
76
|
+
return record.target
|
|
77
|
+
|| record.signOff?.id
|
|
78
|
+
|| record.context?.signOff?.id
|
|
79
|
+
|| record.context?.remediationRoute?.primary
|
|
80
|
+
|| '';
|
|
40
81
|
}
|
|
41
82
|
|
|
42
|
-
|
|
83
|
+
function pendingDedupKey(record = {}) {
|
|
84
|
+
const parts = [record.kind || '', pendingTarget(record), normalizeSummary(record.summary)];
|
|
85
|
+
return `oracle:${sha256(parts.join('\u0000')).slice(0, 16)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function readPendingRecords(projectDir) {
|
|
43
89
|
const file = pendingPath(projectDir);
|
|
44
90
|
if (!fs.existsSync(file)) return [];
|
|
45
91
|
return fs.readFileSync(file, 'utf8')
|
|
@@ -49,15 +95,165 @@ export function listPending(projectDir) {
|
|
|
49
95
|
.filter(Boolean);
|
|
50
96
|
}
|
|
51
97
|
|
|
98
|
+
function sweepPendingRecords(records, now = Date.now()) {
|
|
99
|
+
let changed = false;
|
|
100
|
+
const next = records.map((record) => {
|
|
101
|
+
const dedupKey = record.dedupKey || pendingDedupKey(record);
|
|
102
|
+
if (record.dedupKey !== dedupKey) changed = true;
|
|
103
|
+
const count = Number.isFinite(record.count) && record.count > 0 ? record.count : 1;
|
|
104
|
+
const firstSeenAt = record.firstSeenAt || record.queuedAt || new Date(now).toISOString();
|
|
105
|
+
const lastSeenAt = record.lastSeenAt || record.queuedAt || firstSeenAt;
|
|
106
|
+
const updated = {
|
|
107
|
+
...record,
|
|
108
|
+
dedupKey,
|
|
109
|
+
count,
|
|
110
|
+
firstSeenAt,
|
|
111
|
+
lastSeenAt,
|
|
112
|
+
};
|
|
113
|
+
const ageMs = now - Date.parse(lastSeenAt);
|
|
114
|
+
if (updated.status === 'pending' && Number.isFinite(ageMs) && ageMs > ORACLE_PENDING_TTL_MS) {
|
|
115
|
+
updated.status = 'expired';
|
|
116
|
+
updated.expiredAt = new Date(now).toISOString();
|
|
117
|
+
updated.expiredReason = 'ttl';
|
|
118
|
+
changed = true;
|
|
119
|
+
}
|
|
120
|
+
return updated;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const activePending = next
|
|
124
|
+
.filter((record) => record.status === 'pending')
|
|
125
|
+
.sort((a, b) => Date.parse(b.lastSeenAt || b.queuedAt || 0) - Date.parse(a.lastSeenAt || a.queuedAt || 0));
|
|
126
|
+
|
|
127
|
+
for (const overflow of activePending.slice(ORACLE_PENDING_MAX)) {
|
|
128
|
+
if (overflow.status !== 'pending') continue;
|
|
129
|
+
overflow.status = 'expired';
|
|
130
|
+
overflow.expiredAt = new Date(now).toISOString();
|
|
131
|
+
overflow.expiredReason = 'cap';
|
|
132
|
+
changed = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return { records: next, changed };
|
|
136
|
+
}
|
|
137
|
+
|
|
52
138
|
function rewritePending(projectDir, pending) {
|
|
53
139
|
const file = pendingPath(projectDir);
|
|
140
|
+
ensureOracleDir(projectDir);
|
|
54
141
|
fs.writeFileSync(file, pending.map((p) => JSON.stringify(p)).join('\n') + (pending.length ? '\n' : ''), 'utf8');
|
|
55
142
|
}
|
|
56
143
|
|
|
144
|
+
function enqueuePending(projectDir, record, now = Date.now()) {
|
|
145
|
+
const { records, changed } = sweepPendingRecords(readPendingRecords(projectDir), now);
|
|
146
|
+
const dedupKey = pendingDedupKey(record);
|
|
147
|
+
const queuedAt = new Date(now).toISOString();
|
|
148
|
+
const existing = records.find((entry) => entry.status === 'pending' && entry.dedupKey === dedupKey);
|
|
149
|
+
if (existing) {
|
|
150
|
+
existing.summary = record.summary;
|
|
151
|
+
existing.signOff = record.signOff;
|
|
152
|
+
existing.context = record.context;
|
|
153
|
+
existing.classification = record.classification;
|
|
154
|
+
existing.target = pendingTarget(record);
|
|
155
|
+
existing.count = (Number.isFinite(existing.count) ? existing.count : 1) + 1;
|
|
156
|
+
existing.lastSeenAt = queuedAt;
|
|
157
|
+
rewritePending(projectDir, records);
|
|
158
|
+
return existing;
|
|
159
|
+
}
|
|
160
|
+
const pending = {
|
|
161
|
+
...record,
|
|
162
|
+
id: record.id || `oracle-${dedupKey.slice('oracle:'.length)}`,
|
|
163
|
+
dedupKey,
|
|
164
|
+
count: 1,
|
|
165
|
+
queuedAt,
|
|
166
|
+
firstSeenAt: queuedAt,
|
|
167
|
+
lastSeenAt: queuedAt,
|
|
168
|
+
target: pendingTarget(record),
|
|
169
|
+
};
|
|
170
|
+
rewritePending(projectDir, changed ? records.concat(pending) : [...records, pending]);
|
|
171
|
+
return pending;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function listPending(projectDir, { includeExpired = false, now = Date.now() } = {}) {
|
|
175
|
+
const { records, changed } = sweepPendingRecords(readPendingRecords(projectDir), now);
|
|
176
|
+
if (changed) rewritePending(projectDir, records);
|
|
177
|
+
return records.filter((record) => includeExpired || record.status !== 'expired');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Flush the pending.jsonl backlog to one representative row per dedupKey.
|
|
182
|
+
* Groups every non-approved row (pending or expired — a backlog that has
|
|
183
|
+
* already TTL/cap-expired is still a backlog) by dedupKey, keeps the
|
|
184
|
+
* most-recently-seen row as the survivor with an accumulated
|
|
185
|
+
* occurrenceCount, and archives every other row in the group to
|
|
186
|
+
* pending-archive.jsonl with a supersededBy/supersededAt trail. Approved
|
|
187
|
+
* rows are real decisions already made and are left untouched.
|
|
188
|
+
*/
|
|
189
|
+
export function triagePending(projectDir, { dryRun = false, now = Date.now() } = {}) {
|
|
190
|
+
const { records } = sweepPendingRecords(readPendingRecords(projectDir), now);
|
|
191
|
+
const protectedRecords = records.filter((r) => r.status === 'approved');
|
|
192
|
+
const eligible = records.filter((r) => r.status !== 'approved');
|
|
193
|
+
|
|
194
|
+
const groups = new Map();
|
|
195
|
+
for (const record of eligible) {
|
|
196
|
+
const key = record.dedupKey || pendingDedupKey(record);
|
|
197
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
198
|
+
groups.get(key).push(record);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const survivors = [];
|
|
202
|
+
const supersededRows = [];
|
|
203
|
+
const archivedAt = new Date(now).toISOString();
|
|
204
|
+
|
|
205
|
+
for (const group of groups.values()) {
|
|
206
|
+
const sorted = [...group].sort((a, b) => {
|
|
207
|
+
const bySeen = Date.parse(b.lastSeenAt || b.queuedAt || 0) - Date.parse(a.lastSeenAt || a.queuedAt || 0);
|
|
208
|
+
if (bySeen !== 0) return bySeen;
|
|
209
|
+
return (b.count || 1) - (a.count || 1);
|
|
210
|
+
});
|
|
211
|
+
const [representative, ...rest] = sorted;
|
|
212
|
+
const occurrenceCount = group.reduce((sum, r) => sum + (Number.isFinite(r.count) && r.count > 0 ? r.count : 1), 0);
|
|
213
|
+
const linkedTo = producerLinkageFor(representative.summary);
|
|
214
|
+
|
|
215
|
+
survivors.push({
|
|
216
|
+
...representative,
|
|
217
|
+
count: occurrenceCount,
|
|
218
|
+
occurrenceCount,
|
|
219
|
+
...(rest.length ? { supersededIds: rest.map((r) => r.id) } : {}),
|
|
220
|
+
...(linkedTo ? { producerLinkage: linkedTo } : {}),
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
for (const row of rest) {
|
|
224
|
+
supersededRows.push({
|
|
225
|
+
...row,
|
|
226
|
+
supersededBy: representative.id,
|
|
227
|
+
supersededAt: archivedAt,
|
|
228
|
+
supersededReason: 'duplicate-content-key',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (!dryRun) {
|
|
234
|
+
if (supersededRows.length) {
|
|
235
|
+
ensureOracleDir(projectDir);
|
|
236
|
+
const archiveFile = pendingArchivePath(projectDir);
|
|
237
|
+
const lines = supersededRows.map((r) => JSON.stringify(r)).join('\n') + '\n';
|
|
238
|
+
fs.appendFileSync(archiveFile, lines, 'utf8');
|
|
239
|
+
}
|
|
240
|
+
rewritePending(projectDir, [...protectedRecords, ...survivors]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return {
|
|
244
|
+
dryRun,
|
|
245
|
+
uniqueCount: survivors.length,
|
|
246
|
+
superseded: supersededRows.length,
|
|
247
|
+
survivors,
|
|
248
|
+
supersededRows,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
57
252
|
export async function approvePending(projectDir, id, { execute = true, rootDir, homeDir, dryRun = false } = {}) {
|
|
58
|
-
const pending = listPending(projectDir);
|
|
253
|
+
const pending = listPending(projectDir, { includeExpired: true });
|
|
59
254
|
const match = pending.find((p) => p.id === id);
|
|
60
255
|
if (!match) return { ok: false, reason: 'not-found' };
|
|
256
|
+
if (match.status === 'expired') return { ok: false, reason: 'expired', action: match };
|
|
61
257
|
if (match.status === 'approved' && match.executedAt) {
|
|
62
258
|
return { ok: true, already: true, action: match };
|
|
63
259
|
}
|
|
@@ -147,9 +343,7 @@ export async function runOracleTick({ rootDir, projectDir, homeDir, dryRun = fal
|
|
|
147
343
|
executed.push({ ...rec, classification, result });
|
|
148
344
|
continue;
|
|
149
345
|
}
|
|
150
|
-
const id = `oracle-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
151
346
|
const pending = {
|
|
152
|
-
id,
|
|
153
347
|
kind: rec.kind,
|
|
154
348
|
summary: rec.summary,
|
|
155
349
|
classification,
|
|
@@ -157,8 +351,8 @@ export async function runOracleTick({ rootDir, projectDir, homeDir, dryRun = fal
|
|
|
157
351
|
signOff: rec.signOff ?? signOffMetadata({ id: rec.kind }, projectDir),
|
|
158
352
|
context: { ...rec },
|
|
159
353
|
};
|
|
160
|
-
|
|
161
|
-
queued.push(
|
|
354
|
+
const persisted = dryRun ? pending : enqueuePending(projectDir, pending);
|
|
355
|
+
queued.push(persisted);
|
|
162
356
|
}
|
|
163
357
|
|
|
164
358
|
const tick = {
|
package/lib/oracle/cli.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/oracle/cli.mjs — CLI handlers for `construct oracle <subcommand>`.
|
|
3
3
|
*
|
|
4
|
-
* Subcommands: start, status, review, pending, approve, gaps, reconcile.
|
|
4
|
+
* Subcommands: start, status, review, pending, approve, gaps, reconcile, triage.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import fs from 'node:fs';
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
readLastTick,
|
|
18
18
|
KILLSWITCH_ENV,
|
|
19
19
|
} from './index.mjs';
|
|
20
|
-
import { runOracleTick, listPending, approvePending, defaultRootDir } from './actions.mjs';
|
|
20
|
+
import { runOracleTick, listPending, approvePending, triagePending, defaultRootDir } from './actions.mjs';
|
|
21
21
|
import { collectOracleGaps, formatOracleGapsReport } from './gaps.mjs';
|
|
22
22
|
import { reconcileOracleHygieneBeads } from './reconcile.mjs';
|
|
23
23
|
import { doctorRoot } from '../config/xdg.mjs';
|
|
@@ -153,6 +153,26 @@ function cmdGaps(args, { projectDir }) {
|
|
|
153
153
|
return 0;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
function cmdTriage(args, { projectDir }) {
|
|
157
|
+
const dryRun = args.includes('--dry-run');
|
|
158
|
+
const json = args.includes('--json');
|
|
159
|
+
const result = triagePending(projectDir, { dryRun });
|
|
160
|
+
if (json) {
|
|
161
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
process.stdout.write(
|
|
165
|
+
`Oracle triage${dryRun ? ' (dry-run)' : ''}: ${result.uniqueCount} unique decision(s), ${result.superseded} superseded\n`,
|
|
166
|
+
);
|
|
167
|
+
for (const s of result.survivors) {
|
|
168
|
+
process.stdout.write(` ${s.id} [${s.status}] x${s.occurrenceCount}: ${s.summary}\n`);
|
|
169
|
+
if (s.producerLinkage) {
|
|
170
|
+
process.stdout.write(` producer linkage: ${s.producerLinkage.join(', ')} (approving this does not fix the producer)\n`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
156
176
|
async function cmdReconcile(args, { projectDir }) {
|
|
157
177
|
const dryRun = args.includes('--dry-run');
|
|
158
178
|
const json = args.includes('--json');
|
|
@@ -194,9 +214,10 @@ export async function runOracleCli(args, opts = {}) {
|
|
|
194
214
|
case 'approve': return cmdApprove(subArgs, { projectDir, rootDir, homeDir });
|
|
195
215
|
case 'gaps': return cmdGaps(subArgs, { projectDir });
|
|
196
216
|
case 'reconcile': return cmdReconcile(subArgs, { projectDir });
|
|
217
|
+
case 'triage': return cmdTriage(subArgs, { projectDir });
|
|
197
218
|
default:
|
|
198
219
|
process.stderr.write(
|
|
199
|
-
'Usage: construct oracle <start|status|review|pending|approve|gaps|reconcile> [--json] [--dry-run] [--foreground] [--no-execute]\n',
|
|
220
|
+
'Usage: construct oracle <start|status|review|pending|approve|gaps|reconcile|triage> [--json] [--dry-run] [--foreground] [--no-execute]\n',
|
|
200
221
|
);
|
|
201
222
|
if (sub && sub !== '--help' && sub !== '-h') {
|
|
202
223
|
process.stderr.write(`Unknown subcommand: ${sub}\n`);
|
|
@@ -7,8 +7,14 @@
|
|
|
7
7
|
|
|
8
8
|
import { runOracleDaemon } from './index.mjs';
|
|
9
9
|
import { defaultRootDir } from './actions.mjs';
|
|
10
|
+
import { enableSecretAuditTrail } from '../providers/secret-audit-wiring.mjs';
|
|
10
11
|
|
|
11
12
|
const rootDir = process.env.CONSTRUCT_ORACLE_ROOT || defaultRootDir();
|
|
12
13
|
const projectDir = process.env.CONSTRUCT_ORACLE_PROJECT || process.cwd();
|
|
13
14
|
|
|
15
|
+
// A long-lived daemon in its own process: wire the audit sink at the entry so any
|
|
16
|
+
// credential resolution the tick performs is recorded on the shared trail rather than
|
|
17
|
+
// escaping it, matching the CLI entrypoint's own wiring.
|
|
18
|
+
enableSecretAuditTrail();
|
|
19
|
+
|
|
14
20
|
await runOracleDaemon({ rootDir, projectDir });
|
package/lib/oracle/dispatch.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/oracle/dispatch.mjs —
|
|
2
|
+
* lib/oracle/dispatch.mjs — fleet-health routing artifacts for cx-orchestrator's
|
|
3
|
+
* absorbed cx-oracle duty (deterministic, LLM-free; construct-rf26.11).
|
|
3
4
|
*
|
|
4
5
|
* Writes audit markdown under .cx/oracle/routing/ for approve-gated dispatch.
|
|
5
6
|
* Optional high-severity auto-artifact on tick when gaps are load-bearing.
|
package/lib/oracle/execute.mjs
CHANGED
|
@@ -159,11 +159,11 @@ export async function executeApprovedAction(action, { rootDir, projectDir, homeD
|
|
|
159
159
|
});
|
|
160
160
|
case 'trace-review':
|
|
161
161
|
return dispatchRemediation({
|
|
162
|
-
action, projectDir, homeDir, personaId: 'cx-
|
|
162
|
+
action, projectDir, homeDir, personaId: 'cx-reviewer', eventType: 'outcomes.degraded',
|
|
163
163
|
});
|
|
164
164
|
case 'doctor-followup':
|
|
165
165
|
return dispatchRemediation({
|
|
166
|
-
action, projectDir, homeDir, personaId: 'cx-
|
|
166
|
+
action, projectDir, homeDir, personaId: 'cx-operations', eventType: 'service.down',
|
|
167
167
|
});
|
|
168
168
|
case 'executive-signoff-required':
|
|
169
169
|
case 'structure-cleanup-proposal':
|
package/lib/oracle/gaps.mjs
CHANGED
|
@@ -10,10 +10,10 @@ import { isVerdictOnlyGap, autoRaiseEnabledForGap } from './policy.mjs';
|
|
|
10
10
|
|
|
11
11
|
const HYGIENE_HINTS = {
|
|
12
12
|
'beads-hygiene': 'Run `construct beads drift` to inspect stuck/stale beads.',
|
|
13
|
-
'workflow-misaligned': 'Run `construct workflow
|
|
13
|
+
'workflow-misaligned': 'Run `construct workflow new` or `construct init` to align workflow state.',
|
|
14
14
|
'propagation-stale': 'Run `construct sync` to refresh platform propagation.',
|
|
15
|
-
'census-stale': 'Run `
|
|
16
|
-
'outcomes-missing': 'Run `construct outcomes
|
|
15
|
+
'census-stale': 'Run `node scripts/alignment/census.mjs` to refresh the fleet census.',
|
|
16
|
+
'outcomes-missing': 'Run `construct oracle` action `outcomes-aggregate` when outcome data is available.',
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|