@geraldmaron/construct 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +57 -45
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +273 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +104 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
package/lib/resources/budget.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/resources/budget.mjs — disk-budget enforcement for
|
|
2
|
+
* lib/resources/budget.mjs — disk-budget enforcement for Construct's
|
|
3
|
+
* generated project state.
|
|
3
4
|
*
|
|
4
5
|
* Construct generates data continuously: traces, intake archives, task
|
|
5
6
|
* graphs, worker logs, session records, backups. None of it has a
|
|
6
|
-
* natural ceiling, so left unchecked the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* natural ceiling, so left unchecked the tree grows until it threatens
|
|
8
|
+
* the operator's disk. The budget primitive caps each category against
|
|
9
|
+
* a config-defined ceiling.
|
|
9
10
|
*
|
|
10
11
|
* Two enforcement modes:
|
|
11
12
|
* - **Hard-reject** (traces, worker logs): observability is
|
|
@@ -17,6 +18,13 @@
|
|
|
17
18
|
* load-bearing — never reject; emit a warning the operator can
|
|
18
19
|
* see in the doctor surface, and pruning gets aggressive.
|
|
19
20
|
*
|
|
21
|
+
* Category locations (ADR-0066): traces and worker-logs are the two
|
|
22
|
+
* hard-reject, highest-volume categories, so they resolve through the
|
|
23
|
+
* machine-scoped state root (`resolveStateDir`) rather than the
|
|
24
|
+
* project's own `.cx/`. The remaining soft-warn categories are smaller
|
|
25
|
+
* and still project-local. "Total .cx/ cap" therefore really means
|
|
26
|
+
* "total generated-state cap" and sums bytes across both locations.
|
|
27
|
+
*
|
|
20
28
|
* Defaults live in DEFAULT_PROJECT_CONFIG.resources. The operator
|
|
21
29
|
* raises them in construct.config.json or via the dashboard. Doctor
|
|
22
30
|
* surfaces usage vs cap at 80% (warn) and 100% (fail).
|
|
@@ -24,13 +32,17 @@
|
|
|
24
32
|
import fs from 'node:fs';
|
|
25
33
|
import path from 'node:path';
|
|
26
34
|
import { loadProjectConfig } from '../config/project-config.mjs';
|
|
35
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
27
36
|
|
|
28
37
|
export const HARD_REJECT_CATEGORIES = new Set(['traces', 'worker-logs']);
|
|
29
38
|
export const SOFT_WARN_CATEGORIES = new Set(['intake-archive', 'task-graphs', 'sessions', 'backups', 'handoffs']);
|
|
30
39
|
|
|
31
|
-
const
|
|
32
|
-
traces: '
|
|
33
|
-
'worker-logs': '
|
|
40
|
+
const STATE_ROOT_CATEGORY_SEGMENTS = {
|
|
41
|
+
traces: ['traces'],
|
|
42
|
+
'worker-logs': ['runtime', 'worker'],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const PROJECT_LOCAL_CATEGORY_PATHS = {
|
|
34
46
|
'intake-archive': '.cx/intake/processed',
|
|
35
47
|
'intake-skipped': '.cx/intake/skipped',
|
|
36
48
|
'task-graphs': '.cx/task-graphs',
|
|
@@ -39,6 +51,19 @@ const CATEGORY_PATHS = {
|
|
|
39
51
|
handoffs: '.cx/handoffs',
|
|
40
52
|
};
|
|
41
53
|
|
|
54
|
+
const CATEGORY_NAMES = [...Object.keys(STATE_ROOT_CATEGORY_SEGMENTS), ...Object.keys(PROJECT_LOCAL_CATEGORY_PATHS)];
|
|
55
|
+
|
|
56
|
+
// ensureDir:false — this module only measures/prunes usage, it never itself
|
|
57
|
+
// writes a trace or worker-log file, so a measurement call must not conjure
|
|
58
|
+
// the directory it is merely checking.
|
|
59
|
+
|
|
60
|
+
function categoryDir(projectRoot, name) {
|
|
61
|
+
const stateSegments = STATE_ROOT_CATEGORY_SEGMENTS[name];
|
|
62
|
+
if (stateSegments) return resolveStateDir(projectRoot, ...stateSegments, { ensureDir: false });
|
|
63
|
+
const rel = PROJECT_LOCAL_CATEGORY_PATHS[name];
|
|
64
|
+
return rel ? path.join(projectRoot, rel) : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
42
67
|
function loadResourceBudgets(projectRoot, env) {
|
|
43
68
|
const { config } = loadProjectConfig(projectRoot, env);
|
|
44
69
|
return config?.resources ?? {};
|
|
@@ -67,13 +92,13 @@ function walkDirSize(dir) {
|
|
|
67
92
|
export function measureUsage(projectRoot, env = process.env) {
|
|
68
93
|
const budgets = loadResourceBudgets(projectRoot, env);
|
|
69
94
|
const result = { categories: {}, totalCxBytes: 0, totalCxCap: (budgets?.disk?.totalCxMaxMb ?? 2000) * 1024 * 1024 };
|
|
70
|
-
for (const
|
|
71
|
-
const full =
|
|
95
|
+
for (const name of CATEGORY_NAMES) {
|
|
96
|
+
const full = categoryDir(projectRoot, name);
|
|
72
97
|
const { bytes, files } = walkDirSize(full);
|
|
73
98
|
result.categories[name] = {
|
|
74
99
|
bytes,
|
|
75
100
|
files,
|
|
76
|
-
path:
|
|
101
|
+
path: full,
|
|
77
102
|
enforcement: HARD_REJECT_CATEGORIES.has(name) ? 'hard-reject' : 'soft-warn',
|
|
78
103
|
};
|
|
79
104
|
result.totalCxBytes += bytes;
|
|
@@ -93,7 +118,7 @@ export function warnBudgetOnce(message, env = process.env) {
|
|
|
93
118
|
}
|
|
94
119
|
|
|
95
120
|
export function reserveOrReject(projectRoot, category, sizeBytes, env = process.env) {
|
|
96
|
-
if (!
|
|
121
|
+
if (!CATEGORY_NAMES.includes(category)) {
|
|
97
122
|
return { ok: true, source: 'unknown-category' };
|
|
98
123
|
}
|
|
99
124
|
const usage = measureUsage(projectRoot, env);
|
|
@@ -123,10 +148,8 @@ export function reserveOrReject(projectRoot, category, sizeBytes, env = process.
|
|
|
123
148
|
function listHardRejectFiles(projectRoot) {
|
|
124
149
|
const files = [];
|
|
125
150
|
for (const cat of HARD_REJECT_CATEGORIES) {
|
|
126
|
-
const
|
|
127
|
-
if (!
|
|
128
|
-
const dir = path.join(projectRoot, rel);
|
|
129
|
-
if (!fs.existsSync(dir)) continue;
|
|
151
|
+
const dir = categoryDir(projectRoot, cat);
|
|
152
|
+
if (!dir || !fs.existsSync(dir)) continue;
|
|
130
153
|
const walk = (current) => {
|
|
131
154
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
132
155
|
const full = path.join(current, entry.name);
|
|
@@ -199,7 +222,7 @@ export function planPrune(projectRoot, env = process.env, opts = {}) {
|
|
|
199
222
|
const actions = [];
|
|
200
223
|
|
|
201
224
|
const tracesMaxDays = disk.tracesMaxDays ?? 30;
|
|
202
|
-
const tracesDir =
|
|
225
|
+
const tracesDir = categoryDir(projectRoot, 'traces');
|
|
203
226
|
if (fs.existsSync(tracesDir)) {
|
|
204
227
|
const cutoff = now - tracesMaxDays * 24 * 60 * 60 * 1000;
|
|
205
228
|
for (const f of fs.readdirSync(tracesDir)) {
|
|
@@ -258,7 +281,7 @@ export function planPrune(projectRoot, env = process.env, opts = {}) {
|
|
|
258
281
|
}
|
|
259
282
|
}
|
|
260
283
|
|
|
261
|
-
const workerDir =
|
|
284
|
+
const workerDir = categoryDir(projectRoot, 'worker-logs');
|
|
262
285
|
const workerMaxMb = disk.workerLogsMaxMb ?? 100;
|
|
263
286
|
const workerMaxDays = disk.workerLogsMaxDays ?? 14;
|
|
264
287
|
if (fs.existsSync(workerDir)) {
|
|
@@ -7,33 +7,39 @@
|
|
|
7
7
|
*
|
|
8
8
|
* baseOnly: classifier value "core" loads the base role file (no .flavor suffix).
|
|
9
9
|
* See ADR-0047 for when to split specialists vs add flavors.
|
|
10
|
+
*
|
|
11
|
+
* construct-rf26.11 folded 15 of the 29 legacy specialists into 11 surviving
|
|
12
|
+
* worker anchors as skill bundles rather than separate personas. rolePrefix
|
|
13
|
+
* and classifierKey are unchanged (the skills/roles/*.md overlay files and
|
|
14
|
+
* the classifier heuristics that detect them are untouched); only
|
|
15
|
+
* specialistId is repointed to the anchor that now loads that overlay.
|
|
10
16
|
*/
|
|
11
17
|
|
|
12
18
|
export const SPECIALIST_FLAVOR_BINDINGS = {
|
|
13
19
|
engineer: { classifierKey: 'engineer', rolePrefix: 'engineer', specialistId: 'cx-engineer' },
|
|
14
|
-
'ai-engineer': { classifierKey: 'aiEngineer', rolePrefix: 'ai-engineer', specialistId: 'cx-
|
|
15
|
-
'platform-engineer': { classifierKey: 'platformEngineer', rolePrefix: 'platform-engineer', specialistId: 'cx-
|
|
16
|
-
'data-engineer': { classifierKey: 'dataEngineer', rolePrefix: 'data-engineer', specialistId: 'cx-
|
|
20
|
+
'ai-engineer': { classifierKey: 'aiEngineer', rolePrefix: 'ai-engineer', specialistId: 'cx-engineer', baseOnly: true },
|
|
21
|
+
'platform-engineer': { classifierKey: 'platformEngineer', rolePrefix: 'platform-engineer', specialistId: 'cx-engineer', baseOnly: true },
|
|
22
|
+
'data-engineer': { classifierKey: 'dataEngineer', rolePrefix: 'data-engineer', specialistId: 'cx-engineer' },
|
|
17
23
|
architect: { classifierKey: 'architect', rolePrefix: 'architect', specialistId: 'cx-architect' },
|
|
18
24
|
'product-manager': { classifierKey: 'productManager', rolePrefix: 'product-manager', specialistId: 'cx-product-manager' },
|
|
19
|
-
'business-strategist': { classifierKey: 'businessStrategist', rolePrefix: 'business-strategist', specialistId: 'cx-
|
|
25
|
+
'business-strategist': { classifierKey: 'businessStrategist', rolePrefix: 'business-strategist', specialistId: 'cx-product-manager', baseOnly: true },
|
|
20
26
|
qa: { classifierKey: 'qa', rolePrefix: 'qa', specialistId: 'cx-qa' },
|
|
21
|
-
'test-automation': { classifierKey: 'testAutomation', rolePrefix: 'test-automation', specialistId: 'cx-
|
|
27
|
+
'test-automation': { classifierKey: 'testAutomation', rolePrefix: 'test-automation', specialistId: 'cx-qa', baseOnly: true },
|
|
22
28
|
security: { classifierKey: 'security', rolePrefix: 'security', specialistId: 'cx-security' },
|
|
23
29
|
'data-analyst': { classifierKey: 'dataAnalyst', rolePrefix: 'data-analyst', specialistId: 'cx-data-analyst' },
|
|
24
|
-
sre: { classifierKey: 'sre', rolePrefix: 'sre', specialistId: 'cx-
|
|
30
|
+
sre: { classifierKey: 'sre', rolePrefix: 'sre', specialistId: 'cx-operations', baseOnly: true },
|
|
25
31
|
operations: { classifierKey: 'operations', rolePrefix: 'operations', specialistId: 'cx-operations', baseOnly: true },
|
|
26
|
-
'release-manager': { classifierKey: 'releaseManager', rolePrefix: 'release-manager', specialistId: 'cx-
|
|
27
|
-
'docs-keeper': { classifierKey: 'docsKeeper', rolePrefix: 'docs-keeper', specialistId: 'cx-
|
|
32
|
+
'release-manager': { classifierKey: 'releaseManager', rolePrefix: 'release-manager', specialistId: 'cx-operations', baseOnly: true },
|
|
33
|
+
'docs-keeper': { classifierKey: 'docsKeeper', rolePrefix: 'docs-keeper', specialistId: 'cx-operations', baseOnly: true },
|
|
28
34
|
reviewer: { classifierKey: 'reviewer', rolePrefix: 'reviewer', specialistId: 'cx-reviewer' },
|
|
29
|
-
'devil-advocate': { classifierKey: 'devilAdvocate', rolePrefix: 'devil-advocate', specialistId: 'cx-
|
|
30
|
-
evaluator: { classifierKey: 'evaluator', rolePrefix: 'evaluator', specialistId: 'cx-
|
|
31
|
-
'trace-reviewer': { classifierKey: 'traceReviewer', rolePrefix: 'trace-reviewer', specialistId: 'cx-
|
|
35
|
+
'devil-advocate': { classifierKey: 'devilAdvocate', rolePrefix: 'devil-advocate', specialistId: 'cx-reviewer', baseOnly: true },
|
|
36
|
+
evaluator: { classifierKey: 'evaluator', rolePrefix: 'evaluator', specialistId: 'cx-reviewer', baseOnly: true },
|
|
37
|
+
'trace-reviewer': { classifierKey: 'traceReviewer', rolePrefix: 'trace-reviewer', specialistId: 'cx-reviewer', baseOnly: true },
|
|
32
38
|
researcher: { classifierKey: 'researcher', rolePrefix: 'researcher', specialistId: 'cx-researcher' },
|
|
33
|
-
'ux-researcher': { classifierKey: 'uxResearcher', rolePrefix: 'ux-researcher', specialistId: 'cx-
|
|
34
|
-
explorer: { classifierKey: 'explorer', rolePrefix: 'explorer', specialistId: 'cx-
|
|
39
|
+
'ux-researcher': { classifierKey: 'uxResearcher', rolePrefix: 'ux-researcher', specialistId: 'cx-researcher', baseOnly: true },
|
|
40
|
+
explorer: { classifierKey: 'explorer', rolePrefix: 'explorer', specialistId: 'cx-researcher', baseOnly: true },
|
|
35
41
|
designer: { classifierKey: 'designer', rolePrefix: 'designer', specialistId: 'cx-designer' },
|
|
36
|
-
accessibility: { classifierKey: 'accessibility', rolePrefix: 'designer.accessibility', specialistId: 'cx-
|
|
42
|
+
accessibility: { classifierKey: 'accessibility', rolePrefix: 'designer.accessibility', specialistId: 'cx-designer', baseOnly: true },
|
|
37
43
|
debugger: { classifierKey: 'debugger', rolePrefix: 'debugger', specialistId: 'cx-debugger', baseOnly: true },
|
|
38
44
|
};
|
|
39
45
|
|
|
@@ -45,10 +51,26 @@ const BY_SPECIALIST = Object.fromEntries(
|
|
|
45
51
|
Object.entries(SPECIALIST_FLAVOR_BINDINGS).map(([name, b]) => [name, b]),
|
|
46
52
|
);
|
|
47
53
|
|
|
54
|
+
// Reverse index: since construct-rf26.11, several short-name keys above share
|
|
55
|
+
// a single specialistId (e.g. engineer/ai-engineer/platform-engineer/
|
|
56
|
+
// data-engineer all point at cx-engineer). bindingForSpecialist(shortName)
|
|
57
|
+
// only ever returns the entry whose OWN key matches the short name, so a
|
|
58
|
+
// caller resolving a flavor by specialistId (rather than by the historical
|
|
59
|
+
// short name) needs every candidate binding for that id, not just one.
|
|
60
|
+
const BY_SPECIALIST_ID = new Map();
|
|
61
|
+
for (const binding of Object.values(SPECIALIST_FLAVOR_BINDINGS)) {
|
|
62
|
+
if (!BY_SPECIALIST_ID.has(binding.specialistId)) BY_SPECIALIST_ID.set(binding.specialistId, []);
|
|
63
|
+
BY_SPECIALIST_ID.get(binding.specialistId).push(binding);
|
|
64
|
+
}
|
|
65
|
+
|
|
48
66
|
export function bindingForSpecialist(shortName) {
|
|
49
67
|
return BY_SPECIALIST[shortName] ?? null;
|
|
50
68
|
}
|
|
51
69
|
|
|
70
|
+
export function bindingsForSpecialistId(specialistId) {
|
|
71
|
+
return BY_SPECIALIST_ID.get(specialistId) ?? [];
|
|
72
|
+
}
|
|
73
|
+
|
|
52
74
|
export function bindingForClassifier(classifierKey) {
|
|
53
75
|
return BY_CLASSIFIER[classifierKey] ?? null;
|
|
54
76
|
}
|
package/lib/roles/gateway.mjs
CHANGED
|
@@ -196,7 +196,14 @@ export function shouldEscalate(event, manifest, now = Date.now()) {
|
|
|
196
196
|
return { escalate: false, reason: 'below-threshold', count: hits };
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
-
|
|
199
|
+
// The bd client is injectable so tests can intercept the escalation path
|
|
200
|
+
// without reaching the real, shared bd/dolt database (construct-y4iv) --
|
|
201
|
+
// CONSTRUCT_ROLES_ROOT isolates the dedup-fingerprint state but is also set
|
|
202
|
+
// legitimately in production (lib/oracle/execute.mjs), so it can't double as
|
|
203
|
+
// a "we're in a test" signal. Callers that don't inject a client get the real
|
|
204
|
+
// one, so production behavior (and every other existing caller) is unchanged.
|
|
205
|
+
|
|
206
|
+
async function createBdIncident(event, routeResult, { runBd: runBdImpl = runBd } = {}) {
|
|
200
207
|
// Dedup: check if this fingerprint has already been raised as a bead
|
|
201
208
|
if (fingerprintAlreadyRaised(event.fingerprint)) {
|
|
202
209
|
return { success: true, skipped: true, reason: 'fingerprint-already-raised', fingerprint: event.fingerprint };
|
|
@@ -221,7 +228,7 @@ async function createBdIncident(event, routeResult) {
|
|
|
221
228
|
null,
|
|
222
229
|
2
|
|
223
230
|
);
|
|
224
|
-
const result = await
|
|
231
|
+
const result = await runBdImpl(
|
|
225
232
|
['create', title, '-t', 'bug', '-l', labels, '-d', body],
|
|
226
233
|
{ actor: `roles:${routeResult.personaId}`, silent: true, timeoutSeconds: 10, commandTimeoutSeconds: 30 }
|
|
227
234
|
);
|
|
@@ -246,7 +253,7 @@ async function emitToastBestEffort(payload) {
|
|
|
246
253
|
} catch { /* dashboard not loaded; toast is best-effort */ }
|
|
247
254
|
}
|
|
248
255
|
|
|
249
|
-
export async function tryEscalate(event) {
|
|
256
|
+
export async function tryEscalate(event, deps = {}) {
|
|
250
257
|
const r = routeEvent(event);
|
|
251
258
|
if (!r) return { escalated: false, event, reason: 'no-owner' };
|
|
252
259
|
|
|
@@ -267,7 +274,7 @@ export async function tryEscalate(event) {
|
|
|
267
274
|
}
|
|
268
275
|
} catch { /* cost ledger optional — never block on missing module */ }
|
|
269
276
|
|
|
270
|
-
const bd = await createBdIncident(event, r);
|
|
277
|
+
const bd = await createBdIncident(event, r, deps);
|
|
271
278
|
if (!bd.success) {
|
|
272
279
|
return { escalated: false, event, reason: 'bd-create-failed', error: bd.error };
|
|
273
280
|
}
|
|
@@ -299,12 +306,12 @@ export async function tryEscalate(event) {
|
|
|
299
306
|
return { escalated: true, event, personaId: r.personaId, bdIssueId: bd.issueId, decision };
|
|
300
307
|
}
|
|
301
308
|
|
|
302
|
-
export async function recordAndMaybeInvoke(eventType, payload = {}) {
|
|
309
|
+
export async function recordAndMaybeInvoke(eventType, payload = {}, deps = {}) {
|
|
303
310
|
if (process.env.CONSTRUCT_ROLES === 'off') {
|
|
304
311
|
return { recorded: false, escalated: false, reason: 'global-off' };
|
|
305
312
|
}
|
|
306
313
|
const event = emit(eventType, payload);
|
|
307
|
-
const r = await tryEscalate(event);
|
|
314
|
+
const r = await tryEscalate(event, deps);
|
|
308
315
|
return { recorded: true, ...r };
|
|
309
316
|
}
|
|
310
317
|
|
|
@@ -366,7 +373,7 @@ export function prunePending({ now = Date.now() } = {}) {
|
|
|
366
373
|
// events.jsonl, runs each unescalated one through tryEscalate. Bounded by
|
|
367
374
|
// `sinceMs` and `maxProcess` so a long-quiet machine can't trigger a flood.
|
|
368
375
|
|
|
369
|
-
export async function processBacklog({ sinceMs = 60 * 60 * 1000, maxProcess = 10 } = {}) {
|
|
376
|
+
export async function processBacklog({ sinceMs = 60 * 60 * 1000, maxProcess = 10, deps = {} } = {}) {
|
|
370
377
|
if (process.env.CONSTRUCT_ROLES === 'off') return { processed: 0, escalated: 0, reason: 'global-off' };
|
|
371
378
|
|
|
372
379
|
const ep = _paths.eventsPath();
|
|
@@ -390,7 +397,7 @@ export async function processBacklog({ sinceMs = 60 * 60 * 1000, maxProcess = 10
|
|
|
390
397
|
for (const e of candidates.reverse()) {
|
|
391
398
|
if (processed >= maxProcess) break;
|
|
392
399
|
processed++;
|
|
393
|
-
const r = await tryEscalate(e);
|
|
400
|
+
const r = await tryEscalate(e, deps);
|
|
394
401
|
if (r.escalated) escalated++;
|
|
395
402
|
}
|
|
396
403
|
return { processed, escalated };
|
package/lib/roots.mjs
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/roots.mjs — canonical resolution of the three directory concepts in Construct.
|
|
3
|
+
*
|
|
4
|
+
* packageRoot — where the construct npm package is installed. Resolved from
|
|
5
|
+
* import.meta.url, stable across global install, npx, and local
|
|
6
|
+
* dev symlinks. Points to the directory that contains
|
|
7
|
+
* construct's own package.json. Locate bundled assets here:
|
|
8
|
+
* personas, skills, templates, schemas, hooks.
|
|
9
|
+
*
|
|
10
|
+
* projectRoot — the user's repo being operated on. Discovered at runtime by
|
|
11
|
+
* inspecting --project <dir> in argv, then walking up from cwd
|
|
12
|
+
* looking for .cx/ or package.json. Falls back to cwd when no
|
|
13
|
+
* marker is found. Services (oracle, embed, telemetry) that
|
|
14
|
+
* read or write to the user's repo must use this, not
|
|
15
|
+
* packageRoot.
|
|
16
|
+
*
|
|
17
|
+
* cwd — process.cwd(). Useful for relative-path resolution before
|
|
18
|
+
* projectRoot is determined. Callers should prefer
|
|
19
|
+
* projectRoot once it is resolved.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import fs from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
import { fileURLToPath } from 'node:url';
|
|
25
|
+
|
|
26
|
+
// Under `bun build --compile`, every module's import.meta.url/dirname resolves
|
|
27
|
+
// inside the bundle's virtual filesystem (`/$bunfs/root/...`) rather than a
|
|
28
|
+
// real on-disk path, so any install-root resolution derived from it collapses
|
|
29
|
+
// to a directory that does not exist and every sibling-data-dir read (skills/,
|
|
30
|
+
// specialists/, templates/, config/, registry/) throws ENOENT. process.execPath
|
|
31
|
+
// still reports the compiled binary's real on-disk location, so callers whose
|
|
32
|
+
// naive resolution lands inside /$bunfs fall back to the binary's own
|
|
33
|
+
// directory. This assumes the data directories ship next to the binary
|
|
34
|
+
// (true for the CI/dev smoke path — dist/<binary> sits one level below the
|
|
35
|
+
// checkout root, the same depth bin/construct and lib/roots.mjs assume); a
|
|
36
|
+
// fully standalone end-user install still needs its own asset story
|
|
37
|
+
// (embedded assets or a co-installed data tree), tracked separately.
|
|
38
|
+
|
|
39
|
+
const BUN_VIRTUAL_FS_PREFIX = '/$bunfs';
|
|
40
|
+
|
|
41
|
+
export function isBunCompiledVirtualPath(candidatePath) {
|
|
42
|
+
return typeof process.versions?.bun === 'string' && candidatePath.startsWith(BUN_VIRTUAL_FS_PREFIX);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function resolveInstallRoot(naiveRoot, { execPath = process.execPath, upFromBinary = 1 } = {}) {
|
|
46
|
+
if (!isBunCompiledVirtualPath(naiveRoot)) return naiveRoot;
|
|
47
|
+
return path.resolve(path.dirname(execPath), ...Array(upFromBinary).fill('..'));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Two dozen lib/*.mjs files use the standard Node "was I run directly"
|
|
51
|
+
// idiom (`import.meta.url === file://${process.argv[1]}`) so they double as
|
|
52
|
+
// standalone scripts. Under a Bun-compiled binary, every bundled module's
|
|
53
|
+
// import.meta.url collapses to the same virtual bunfs path as process.argv[1]
|
|
54
|
+
// (both point at the single compiled executable), so that comparison is true
|
|
55
|
+
// for every one of those files simultaneously — each runs its own top-level
|
|
56
|
+
// CLI logic as an unwanted side effect of merely being imported, and the
|
|
57
|
+
// first one whose logic throws (e.g. lib/headhunt.mjs's bare invocation
|
|
58
|
+
// requiring --for) takes the whole binary down before bin/construct's real
|
|
59
|
+
// dispatch ever executes. There is exactly one legitimate entry point inside
|
|
60
|
+
// a compiled binary (bin/construct itself), so this always resolves false
|
|
61
|
+
// under Bun-compile regardless of the module asking.
|
|
62
|
+
|
|
63
|
+
export function isMainModule(moduleUrl) {
|
|
64
|
+
if (typeof process.versions?.bun === 'string' && isBunCompiledVirtualPath(process.argv[1] || '')) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
return moduleUrl === `file://${process.argv[1]}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// lib/roots.mjs lives one level below the package root (<package>/lib/roots.mjs).
|
|
71
|
+
|
|
72
|
+
export const packageRoot = resolveInstallRoot(path.resolve(fileURLToPath(import.meta.url), '..', '..'));
|
|
73
|
+
|
|
74
|
+
// Walk upward from `start`, looking for .cx/ first (Construct project marker),
|
|
75
|
+
// then package.json (generic JS project root). Stops at filesystem root.
|
|
76
|
+
|
|
77
|
+
function walkUp(start) {
|
|
78
|
+
let dir = path.resolve(start);
|
|
79
|
+
while (true) {
|
|
80
|
+
if (fs.existsSync(path.join(dir, '.cx'))) return dir;
|
|
81
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
82
|
+
const parent = path.dirname(dir);
|
|
83
|
+
if (parent === dir) return null;
|
|
84
|
+
dir = parent;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Resolve the project root for the user's repo.
|
|
90
|
+
*
|
|
91
|
+
* Resolution order:
|
|
92
|
+
* 1. --project <dir> in process.argv (explicit override)
|
|
93
|
+
* 2. Walk upward from cwd looking for .cx/ or package.json
|
|
94
|
+
* 3. cwd itself as a last resort
|
|
95
|
+
*
|
|
96
|
+
* Pass a cwd argument when the caller controls the starting directory (tests,
|
|
97
|
+
* sub-process workers). Omit it in top-level CLI code to use process.cwd().
|
|
98
|
+
*/
|
|
99
|
+
export function resolveProjectRoot(cwd = process.cwd()) {
|
|
100
|
+
const argv = process.argv;
|
|
101
|
+
const flagIndex = argv.indexOf('--project');
|
|
102
|
+
if (flagIndex !== -1 && argv[flagIndex + 1]) {
|
|
103
|
+
return path.resolve(argv[flagIndex + 1]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return walkUp(cwd) ?? path.resolve(cwd);
|
|
107
|
+
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/runtime/uv-bootstrap.mjs — idempotent uv + docling venv provisioner.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* One venv per machine, not one per project (construct-rf26.16; ADR-0066
|
|
5
|
+
* called this out as the docling venv's terminal shape). On first use:
|
|
6
|
+
* downloads uv via the official Astral installer, creates
|
|
7
|
+
* `~/.construct/runtime/docling/.venv` (or `$CX_TOOLKIT_DIR/runtime/docling/.venv`)
|
|
8
|
+
* via `resolveSharedRuntimeDir`, installs the pinned docling release.
|
|
9
|
+
* Subsequent calls from any project on this machine are no-ops once the
|
|
10
|
+
* venv is present — there is no project key anywhere in this path.
|
|
11
|
+
*
|
|
12
|
+
* Version pin is the lockfile: `.install-marker.json`'s `doclingVersion`
|
|
13
|
+
* field is compared against DOCLING_PIN on every call. A mismatch (a
|
|
14
|
+
* construct upgrade that bumps the pin) is not silent drift — it clears the
|
|
15
|
+
* stale venv directory and re-provisions from scratch under `force`
|
|
16
|
+
* semantics, rather than layering a new install over an old one.
|
|
7
17
|
*
|
|
8
18
|
* Best-practice notes (2026-06):
|
|
9
19
|
* - uv (Astral) is the consensus Python tooling, ~10× faster than pip, single
|
|
@@ -16,19 +26,25 @@
|
|
|
16
26
|
* Returns the path to the venv's python binary so callers can spawn the
|
|
17
27
|
* sidecar without relying on PATH state.
|
|
18
28
|
*/
|
|
19
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
20
30
|
import { spawn, spawnSync } from 'node:child_process';
|
|
21
31
|
import path from 'node:path';
|
|
22
32
|
import os from 'node:os';
|
|
23
33
|
|
|
34
|
+
import { resolveSharedRuntimeDir } from '../state-root.mjs';
|
|
35
|
+
|
|
24
36
|
const DOCLING_PIN = '2.45.0';
|
|
25
37
|
const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh';
|
|
26
38
|
const UV_TIMEOUT_MS = 120_000;
|
|
27
39
|
const VENV_TIMEOUT_MS = 240_000;
|
|
28
40
|
const INSTALL_TIMEOUT_MS = 600_000;
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
42
|
+
// ensureDir:false — shared by the provisioner (ensureDoclingVenv, which
|
|
43
|
+
// already mkdirs before it writes) and the read-only detector
|
|
44
|
+
// (describeDoclingRuntime); a detect-only call must not conjure the dir.
|
|
45
|
+
|
|
46
|
+
export function defaultRuntimeDir() {
|
|
47
|
+
return resolveSharedRuntimeDir('docling', { ensureDir: false });
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
function which(bin) {
|
|
@@ -127,6 +143,16 @@ export async function ensureDoclingVenv({ runtimeDir = defaultRuntimeDir(), forc
|
|
|
127
143
|
return { pythonBin: existingPython, venvDir, runtimeDir, fresh: false };
|
|
128
144
|
}
|
|
129
145
|
|
|
146
|
+
// A version-pin mismatch (existingMarker present but doclingVersion !== DOCLING_PIN)
|
|
147
|
+
// is a controlled re-provision, not silent drift: clear the stale venv outright
|
|
148
|
+
// rather than trust `uv venv` to reconcile an existing directory left by a
|
|
149
|
+
// different docling/Python pin.
|
|
150
|
+
|
|
151
|
+
if (existingMarker && existingMarker.doclingVersion !== DOCLING_PIN) {
|
|
152
|
+
progress(`Pinned docling version changed (${existingMarker.doclingVersion} → ${DOCLING_PIN}) — re-provisioning.`);
|
|
153
|
+
try { rmSync(venvDir, { recursive: true, force: true }); } catch { /* best effort */ }
|
|
154
|
+
}
|
|
155
|
+
|
|
130
156
|
progress('Provisioning the docling document extractor (first run). This downloads a Python runtime and ML dependencies and can take several minutes; later runs are instant.');
|
|
131
157
|
const uv = await ensureUv(runtimeDir);
|
|
132
158
|
|
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
*
|
|
13
13
|
* Model files are downloaded directly from Hugging Face the first time the
|
|
14
14
|
* default `base.en` (or env-overridden) model is requested. Models cache at
|
|
15
|
-
* <
|
|
16
|
-
*
|
|
15
|
+
* <stateRoot>/runtime/whisper/models/ (ADR-0066: machine-scoped, not
|
|
16
|
+
* project-local). base.en is ~150MB; smaller than the small.en or medium.en
|
|
17
|
+
* model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
|
|
17
18
|
*
|
|
18
19
|
* No bundled distribution: whisper.cpp upstream does not publish stable
|
|
19
20
|
* pre-built CLI binaries across platforms, and building from source via
|
|
@@ -25,12 +26,19 @@ import { pipeline } from 'node:stream/promises';
|
|
|
25
26
|
import path from 'node:path';
|
|
26
27
|
import os from 'node:os';
|
|
27
28
|
|
|
29
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
30
|
+
|
|
28
31
|
const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
|
|
29
32
|
const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
|
|
30
33
|
const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
|
|
31
34
|
|
|
35
|
+
// ensureDir:false — shared by the provisioner (ensureWhisperRuntime, which
|
|
36
|
+
// already mkdirs before it writes) and the read-only detector
|
|
37
|
+
// (describeWhisperRuntime/locateWhisperBinary); a detect-only call must not
|
|
38
|
+
// conjure the dir.
|
|
39
|
+
|
|
32
40
|
export function defaultRuntimeDir(cwd = process.cwd()) {
|
|
33
|
-
return
|
|
41
|
+
return resolveStateDir(cwd, 'runtime', 'whisper', { ensureDir: false });
|
|
34
42
|
}
|
|
35
43
|
|
|
36
44
|
function which(bin) {
|
package/lib/runtime-env.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* lib/runtime-env.mjs — Construct runtime environment with credential bootstrap.
|
|
3
3
|
*
|
|
4
|
-
* prepareConstructEnv merges ~/.config/construct/config.env
|
|
5
|
-
*
|
|
4
|
+
* prepareConstructEnv merges the user's ~/.config/construct/config.env and,
|
|
5
|
+
* when the caller passes `rootDir` (the resolved project root, not the
|
|
6
|
+
* toolkit install checkout), the project .env into the process environment —
|
|
7
|
+
* project .env wins. Credential auto-link from 1Password runs only when
|
|
8
|
+
* autoLink:true (setup-credentials).
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import { loadConstructEnv } from './env-config.mjs';
|
package/lib/scheduler/index.mjs
CHANGED
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Jobs registered here: 'tag-candidate-mining', 'skill-usage-rollup', 'doc-hygiene-scan'
|
|
10
10
|
*
|
|
11
|
-
* Deployment mode is
|
|
11
|
+
* Deployment mode is resolved through the canonical lib/deployment-mode.mjs
|
|
12
|
+
* getDeploymentMode (CONSTRUCT_DEPLOYMENT_MODE env, then project config, then solo),
|
|
13
|
+
* so the scheduler agrees with the broker, session prelude, and status surfaces.
|
|
12
14
|
* Registration is in-memory: no persistence across process restarts beyond the
|
|
13
15
|
* native trigger files written by solo.mjs or the DB rows in 009_scheduler.sql.
|
|
14
16
|
*/
|
|
15
17
|
|
|
18
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
19
|
+
|
|
16
20
|
// ---------------------------------------------------------------------------
|
|
17
21
|
// In-memory job registry
|
|
18
22
|
// ---------------------------------------------------------------------------
|
|
@@ -57,22 +61,6 @@ export function listJobs() {
|
|
|
57
61
|
}));
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
/**
|
|
61
|
-
* Derives the deployment mode from environment variables.
|
|
62
|
-
* Returns 'enterprise' | 'team' | 'solo'.
|
|
63
|
-
*
|
|
64
|
-
* CONSTRUCT_DEPLOYMENT=enterprise or CONSTRUCT_ENTERPRISE=1 => enterprise
|
|
65
|
-
* CONSTRUCT_DEPLOYMENT=team or CONSTRUCT_TEAM=1 => team
|
|
66
|
-
* Otherwise => solo
|
|
67
|
-
*/
|
|
68
|
-
export function getDeploymentMode(env = process.env) {
|
|
69
|
-
const explicit = env.CONSTRUCT_DEPLOYMENT;
|
|
70
|
-
if (explicit === 'enterprise' || env.CONSTRUCT_ENTERPRISE === '1') return 'enterprise';
|
|
71
|
-
if (explicit === 'team' || env.CONSTRUCT_TEAM === '1') return 'team';
|
|
72
|
-
if (explicit === 'solo') return 'solo';
|
|
73
|
-
return 'solo';
|
|
74
|
-
}
|
|
75
|
-
|
|
76
64
|
// ---------------------------------------------------------------------------
|
|
77
65
|
// Built-in job registrations
|
|
78
66
|
// ---------------------------------------------------------------------------
|
|
@@ -104,8 +92,8 @@ registerJob({
|
|
|
104
92
|
const DOC_HYGIENE_SOLO_CRON = '0 2 * * *';
|
|
105
93
|
const DOC_HYGIENE_TEAM_CRON = '0 * * * *';
|
|
106
94
|
|
|
107
|
-
export function resolveDocHygieneSchedule(env = process.env) {
|
|
108
|
-
const mode = getDeploymentMode(env);
|
|
95
|
+
export function resolveDocHygieneSchedule(env = process.env, { cwd } = {}) {
|
|
96
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
109
97
|
const isTeamish = mode === 'team' || mode === 'enterprise';
|
|
110
98
|
return {
|
|
111
99
|
mode: isTeamish ? 'team' : 'solo',
|
|
@@ -121,7 +109,7 @@ registerJob({
|
|
|
121
109
|
mode: docHygiene.mode,
|
|
122
110
|
handler: async ({ cwd, env }) => {
|
|
123
111
|
const { findHygieneCandidates } = await import('../hygiene/scan.mjs');
|
|
124
|
-
const { limit } = resolveDocHygieneSchedule(env);
|
|
112
|
+
const { limit } = resolveDocHygieneSchedule(env, { cwd });
|
|
125
113
|
const candidates = findHygieneCandidates({ cwd, limit });
|
|
126
114
|
return {
|
|
127
115
|
status: 'ok',
|
package/lib/schema-infer.mjs
CHANGED
|
@@ -25,6 +25,7 @@ import { extractDocumentTextNodeNative, extractDocumentTextAsync } from './docum
|
|
|
25
25
|
import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
|
|
26
26
|
import { getUserEnvPath } from './env-config.mjs';
|
|
27
27
|
import { pollFreeModels, topForTier } from './model-free-selector.mjs';
|
|
28
|
+
import { extractOpRef, resolveOpRef, resolveSecret } from './providers/secret-resolver.mjs';
|
|
28
29
|
import { execSync } from 'node:child_process';
|
|
29
30
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
@@ -187,9 +188,37 @@ async function fetchWithTimeout(url, options) {
|
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
191
|
+
function resolveSecretOrNull(varName, { env = process.env, allowAmbient = true } = {}) {
|
|
192
|
+
try {
|
|
193
|
+
return resolveSecret(varName, { env, allowAmbient });
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function materializeConfiguredKey(rawValue) {
|
|
200
|
+
if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
|
|
201
|
+
const ref = extractOpRef(rawValue);
|
|
202
|
+
try {
|
|
203
|
+
return ref ? resolveOpRef(ref) : rawValue;
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resolveOpenRouterApiKey() {
|
|
210
|
+
const envKey = resolveSecretOrNull('OPENROUTER_API_KEY');
|
|
211
|
+
if (envKey) return envKey;
|
|
212
|
+
try {
|
|
213
|
+
return materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
|
|
214
|
+
} catch {
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
190
219
|
async function callModel(userContent, { traceId } = {}) {
|
|
191
|
-
const orKey =
|
|
192
|
-
const anthropicKey =
|
|
220
|
+
const orKey = resolveOpenRouterApiKey();
|
|
221
|
+
const anthropicKey = resolveSecretOrNull('ANTHROPIC_API_KEY');
|
|
193
222
|
|
|
194
223
|
if (!orKey && !anthropicKey) {
|
|
195
224
|
throw new Error('No API key found. Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY to use schema inference.');
|