@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
|
@@ -10,11 +10,16 @@
|
|
|
10
10
|
* `orchestrator` entry). Specialists, slash commands, and skills do NOT
|
|
11
11
|
* land at global scope — they are project content.
|
|
12
12
|
* - Plus the hook installer in `~/.claude/settings.json`, which has to be
|
|
13
|
-
* global so hooks fire in every Claude Code session.
|
|
13
|
+
* global so hooks fire in every Claude Code session. MCP server
|
|
14
|
+
* definitions are a separate file: Claude Code reads user-scope MCP
|
|
15
|
+
* servers from the top-level `mcpServers` object in `~/.claude.json`,
|
|
16
|
+
* not from settings.json (construct-ranh).
|
|
14
17
|
*
|
|
15
18
|
* Project scope (`<project>/.claude/`, `<project>/.codex/`, `<project>/.github/`, …)
|
|
16
19
|
* - `construct` front door only (Single Front Door), slash commands, skills, MCP
|
|
17
20
|
* wiring. Specialists dispatch internally via orchestration MCP tools.
|
|
21
|
+
* MCP server definitions land in `<project>/.mcp.json` (Claude Code's
|
|
22
|
+
* documented project scope), not `.claude/settings.json`.
|
|
18
23
|
*
|
|
19
24
|
* Flags:
|
|
20
25
|
* --dry-run Print a diff of what would change without writing anything.
|
|
@@ -47,6 +52,7 @@ import {
|
|
|
47
52
|
import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig, ensureOpenRouterProviderAuth } from "../lib/opencode-config.mjs";
|
|
48
53
|
import { HEAVY_EXTERNAL_MCP_IDS, LOCAL_SURFACE_MODES, decideTrim, isLocalModel } from "../lib/mcp/tool-budget.mjs";
|
|
49
54
|
import { emitCursorRules } from "../lib/rules-delivery.mjs";
|
|
55
|
+
import { memoryPort } from "../lib/home-namespace.mjs";
|
|
50
56
|
import { resolvePromptContract, readPromptBody } from "../lib/prompt-composer.js";
|
|
51
57
|
import { renderPersonaForTier } from "../lib/persona-sections.mjs";
|
|
52
58
|
import { getModelVerdict } from "../lib/ollama/capability-store.mjs";
|
|
@@ -117,7 +123,6 @@ function unifiedToLegacyRegistry(unified) {
|
|
|
117
123
|
};
|
|
118
124
|
}
|
|
119
125
|
|
|
120
|
-
clearCache();
|
|
121
126
|
clearCache();
|
|
122
127
|
const unified = loadRegistry({ rootDir: root });
|
|
123
128
|
const registry = unifiedToLegacyRegistry(unified);
|
|
@@ -454,7 +459,7 @@ const standardConstructTools = [
|
|
|
454
459
|
// agent's tools list or the call is blocked, leaving the orchestrator unable to route.
|
|
455
460
|
// Single source of truth so every host's orchestrator grant stays in parity.
|
|
456
461
|
|
|
457
|
-
const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run"];
|
|
462
|
+
const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run", "orchestration_delegation_next"];
|
|
458
463
|
const managedStart = `# BEGIN ${systemName.toUpperCase()} AGENTS`;
|
|
459
464
|
const managedEnd = `# END ${systemName.toUpperCase()} AGENTS`;
|
|
460
465
|
const mdManagedStart = `<!-- BEGIN ${systemName.toUpperCase()} AGENTS -->`;
|
|
@@ -486,10 +491,17 @@ function resolveArgs(args) {
|
|
|
486
491
|
: a));
|
|
487
492
|
}
|
|
488
493
|
|
|
489
|
-
|
|
494
|
+
// A secret-suffixed placeholder (TOKEN/SECRET/API_KEY/PUBLIC_KEY/PRIVATE_KEY) always
|
|
495
|
+
// resolves to OpenCode's `{env:NAME}` reference form, even when the named var is set
|
|
496
|
+
// in process.env, so a live credential never lands in a generated config file. This
|
|
497
|
+
// guard runs before the general env lookup below, mirroring the buildLocalEnvironment
|
|
498
|
+
// value->ref flip in lib/mcp-platform-config.mjs. GITHUB_TOKEN is an intentional alias
|
|
499
|
+
// that still materializes GITHUB_PERSONAL_ACCESS_TOKEN's value: that branch stays ahead
|
|
500
|
+
// of the suffix guard since it targets a different source var, not GITHUB_TOKEN itself.
|
|
501
|
+
|
|
502
|
+
export function resolveTemplateStrings(value) {
|
|
490
503
|
if (typeof value === "string") {
|
|
491
504
|
return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => {
|
|
492
|
-
if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
|
|
493
505
|
if (name === "CX_TOOLKIT_DIR") return root;
|
|
494
506
|
if (name === "MEMORY_PORT") return process.env.MEMORY_PORT || "8765";
|
|
495
507
|
if (name === "CONSTRUCT_MEMORY_BRIDGE_URL") {
|
|
@@ -501,6 +513,7 @@ function resolveTemplateStrings(value) {
|
|
|
501
513
|
if (/(?:TOKEN|SECRET|API_KEY|PUBLIC_KEY|PRIVATE_KEY)$/.test(name)) {
|
|
502
514
|
return `{env:${name}}`;
|
|
503
515
|
}
|
|
516
|
+
if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
|
|
504
517
|
return `__${name}__`;
|
|
505
518
|
});
|
|
506
519
|
}
|
|
@@ -773,13 +786,13 @@ function orchestrationMicroPrompt(platform) {
|
|
|
773
786
|
`Example — the user says "add rate limiting to the API". Your first action is a tool call, not prose:\n` +
|
|
774
787
|
` call ${policyTool} { "request": "add rate limiting to the API" }\n` +
|
|
775
788
|
`If the route is focused/orchestrated specialist work, call \`${runTool}\` with the same request. If the route suggests a workflow such as \`research-synthesis\`, pass it as \`workflow_type\`. Do not narrate completed research unless \`${runTool}\` or evidence tools actually ran.\n\n` +
|
|
776
|
-
`If a request needs a capability you lack — live web/network access, external data, code execution —
|
|
789
|
+
`If a request needs a capability you lack — live web/network access, external data, code execution — route it via \`${runTool}\` to the specialist that holds it (the researcher performs live web retrieval when a web path is available). Do not tell the user to run it themselves. But if \`${runTool}\` reports the capability was unavailable (degraded with \`capability-unavailable\`, or a prepare-only result), say plainly it could not be reached and return an insufficient-evidence result — never fabricate URLs, dates, quotes, or citations. Ask one clarifying question when the target is ambiguous.`
|
|
777
790
|
);
|
|
778
791
|
}
|
|
779
792
|
|
|
780
793
|
// Directive for the local editor agent (construct-local). It executes bounded work on
|
|
781
794
|
// the cheap local model and hands planning/reasoning back to the construct architect —
|
|
782
|
-
//
|
|
795
|
+
// a cheap-editor/capable-architect split. Kept short: a small model must actually obey it.
|
|
783
796
|
|
|
784
797
|
const LOCAL_EDITOR_DIRECTIVE =
|
|
785
798
|
`You are a focused execution agent running on a local model, dispatched by construct to do one bounded job. Do well-scoped edits for the current task and verify them; make the smallest correct change, never a broad rewrite.\n` +
|
|
@@ -1017,7 +1030,7 @@ ${buildPrompt(entry, allEntries, "claude")}
|
|
|
1017
1030
|
* Rewrite the home-mode hook command pattern
|
|
1018
1031
|
* node "$HOME/.config/construct/lib/hooks/<name>.mjs"
|
|
1019
1032
|
* into the project-portable form
|
|
1020
|
-
* node "${CLAUDE_PROJECT_DIR
|
|
1033
|
+
* node "${CLAUDE_PROJECT_DIR:-<absRoot>}/.construct/run.mjs" hook <name>
|
|
1021
1034
|
* so the resulting settings.json works on any clone where the project ships
|
|
1022
1035
|
* the .construct/ launcher (committed by `npm install`'s postinstall or by
|
|
1023
1036
|
* `construct init`). The launcher resolves Construct via node_modules → npx
|
|
@@ -1025,21 +1038,29 @@ ${buildPrompt(entry, allEntries, "claude")}
|
|
|
1025
1038
|
* works for non-Node ecosystems too. Other commands (inline node -e
|
|
1026
1039
|
* snippets, npx block-no-verify@…) are left untouched.
|
|
1027
1040
|
*
|
|
1028
|
-
* The `${CLAUDE_PROJECT_DIR
|
|
1041
|
+
* The `${CLAUDE_PROJECT_DIR:-<absRoot>}` anchor matters: hosts invoke hooks with
|
|
1029
1042
|
* a working directory that is not guaranteed to be the project root (observed:
|
|
1030
|
-
* $HOME), and a bare relative `.construct/run.mjs` then
|
|
1031
|
-
* at node:internal/modules/cjs/loader
|
|
1032
|
-
*
|
|
1033
|
-
*
|
|
1043
|
+
* $HOME, a `cd`-ed scratch dir), and a bare relative `.construct/run.mjs` then
|
|
1044
|
+
* fails with MODULE_NOT_FOUND at node:internal/modules/cjs/loader — before any
|
|
1045
|
+
* code in run.mjs can execute, so the shim cannot self-correct. CLAUDE_PROJECT_DIR
|
|
1046
|
+
* is the project root Claude Code exports to every hook (same var lib/hooks/*.mjs
|
|
1047
|
+
* already read) and stays correct if the checkout moves; the fallback is the
|
|
1048
|
+
* absolute project root baked at sync time, so tools that do not export
|
|
1049
|
+
* CLAUDE_PROJECT_DIR (or any host with a drifted cwd) still resolve the launcher.
|
|
1050
|
+
* `.claude/settings.json` is gitignored and regenerated per machine by
|
|
1051
|
+
* `construct sync`, so an absolute fallback is machine-correct without leaking a
|
|
1052
|
+
* shared path. The prior `:-.` fallback was cwd-relative and broke on any drift.
|
|
1034
1053
|
*/
|
|
1035
|
-
function makeHooksPortable(hooksJson) {
|
|
1054
|
+
function makeHooksPortable(hooksJson, projectRoot) {
|
|
1055
|
+
const anchor = `\${CLAUDE_PROJECT_DIR:-${path.resolve(projectRoot)}}`;
|
|
1056
|
+
|
|
1036
1057
|
// Operate on the in-memory object so we don't fight JSON string escaping.
|
|
1037
1058
|
const replaceCommand = (cmd) => {
|
|
1038
1059
|
if (typeof cmd !== 'string') return cmd;
|
|
1039
1060
|
const m = cmd.match(/^node\s+"?\$HOME\/\.config\/construct\/lib\/hooks\/([a-z0-9-]+)\.mjs"?\s*(.*)$/);
|
|
1040
1061
|
if (!m) return cmd;
|
|
1041
1062
|
const [, name, rest] = m;
|
|
1042
|
-
return `node "
|
|
1063
|
+
return `node "${anchor}/.construct/run.mjs" hook ${name}${rest ? ' ' + rest.trim() : ''}`;
|
|
1043
1064
|
};
|
|
1044
1065
|
|
|
1045
1066
|
const walk = (node) => {
|
|
@@ -1092,14 +1113,9 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
|
|
|
1092
1113
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1093
1114
|
if (!GLOBAL_CLAUDE_MCP_IDS.has(id)) continue;
|
|
1094
1115
|
const existingEntry = settings.mcpServers[id];
|
|
1095
|
-
const
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
|
|
1099
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1100
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1101
|
-
if (existingEntry && !hasPlaceholder && !hasFloatingVersion && !transportMismatch) continue;
|
|
1102
|
-
settings.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
1116
|
+
const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
1117
|
+
if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
|
|
1118
|
+
settings.mcpServers[id] = desiredEntry;
|
|
1103
1119
|
}
|
|
1104
1120
|
}
|
|
1105
1121
|
|
|
@@ -1108,6 +1124,12 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
|
|
|
1108
1124
|
* with hook commands rewritten to be path-relative to whatever Construct
|
|
1109
1125
|
* install the project carries. Merges into an existing settings.json
|
|
1110
1126
|
* if one is already in the project; otherwise creates a fresh one.
|
|
1127
|
+
*
|
|
1128
|
+
* Carries hooks and permissions only — Claude Code does not read MCP server
|
|
1129
|
+
* definitions from settings.json at any scope (confirmed against
|
|
1130
|
+
* code.claude.com/docs/en/mcp's installation-scopes table and live `claude
|
|
1131
|
+
* mcp list` repro, construct-ranh). Project-scope MCP wiring is
|
|
1132
|
+
* writeProjectMcpJson's job, targeting `.mcp.json`.
|
|
1111
1133
|
*/
|
|
1112
1134
|
function writeProjectClaudeSettings(targetDir) {
|
|
1113
1135
|
const settingsPath = path.join(targetDir, ".claude", "settings.json");
|
|
@@ -1121,17 +1143,42 @@ function writeProjectClaudeSettings(targetDir) {
|
|
|
1121
1143
|
: {};
|
|
1122
1144
|
|
|
1123
1145
|
if (template.hooks) {
|
|
1124
|
-
existing.hooks = JSON.parse(makeHooksPortable(template.hooks));
|
|
1146
|
+
existing.hooks = JSON.parse(makeHooksPortable(template.hooks, targetDir));
|
|
1125
1147
|
}
|
|
1126
1148
|
if (template.permissions) {
|
|
1127
1149
|
existing.permissions ??= template.permissions;
|
|
1128
1150
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1151
|
+
|
|
1152
|
+
if (DRY_RUN) return;
|
|
1153
|
+
mkdirp(path.dirname(settingsPath));
|
|
1154
|
+
writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Materialise project-scope MCP wiring at `.mcp.json` in the project root —
|
|
1159
|
+
* the scope Claude Code documents for team-shared, version-controlled MCP
|
|
1160
|
+
* server definitions (settings.json only carries policy keys and hooks).
|
|
1161
|
+
* Merges into an existing `.mcp.json` if the project already has one;
|
|
1162
|
+
* existing entries win so manual edits or a prior `claude mcp add --scope
|
|
1163
|
+
* project` stick.
|
|
1164
|
+
*/
|
|
1165
|
+
function writeProjectMcpJson(targetDir) {
|
|
1166
|
+
const mcpJsonPath = path.join(targetDir, ".mcp.json");
|
|
1167
|
+
const templatePath = path.join(root, "platforms", "claude", "settings.template.json");
|
|
1168
|
+
|
|
1169
|
+
const existing = fs.existsSync(mcpJsonPath)
|
|
1170
|
+
? JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"))
|
|
1171
|
+
: {};
|
|
1172
|
+
existing.mcpServers ??= {};
|
|
1173
|
+
|
|
1174
|
+
if (fs.existsSync(templatePath)) {
|
|
1175
|
+
const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
|
|
1176
|
+
if (template.mcpServers) {
|
|
1177
|
+
for (const [id, mcpDef] of Object.entries(template.mcpServers)) {
|
|
1178
|
+
if (existing.mcpServers[id]) continue;
|
|
1179
|
+
if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
|
|
1180
|
+
existing.mcpServers[id] = mcpDef;
|
|
1181
|
+
}
|
|
1135
1182
|
}
|
|
1136
1183
|
}
|
|
1137
1184
|
|
|
@@ -1140,19 +1187,23 @@ function writeProjectClaudeSettings(targetDir) {
|
|
|
1140
1187
|
// the server exposing project_context/get_skill/get_template/orchestration_policy
|
|
1141
1188
|
// that the specialist loop depends on. The curated template omits it, which left
|
|
1142
1189
|
// Claude Code as the only selected tool without the construct config. Merge the
|
|
1143
|
-
// registry on top of the template seed
|
|
1144
|
-
//
|
|
1190
|
+
// registry on top of the template seed via needsRefresh(): an existing entry
|
|
1191
|
+
// wins as long as it still matches the registry (env keys, pinned version,
|
|
1192
|
+
// transport, toolkit path), so a manual opt-in sticks but drift — a missing
|
|
1193
|
+
// registry env key, a stale pinned version — gets corrected.
|
|
1145
1194
|
const registryMcp = scopedManagedMcpDefs({ projectScope: true });
|
|
1146
|
-
existing.mcpServers ??= {};
|
|
1147
1195
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1148
|
-
if (existing.mcpServers[id]) continue;
|
|
1149
1196
|
if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
|
|
1150
|
-
existing.mcpServers[id]
|
|
1197
|
+
const existingEntry = existing.mcpServers[id];
|
|
1198
|
+
const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env);
|
|
1199
|
+
if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
|
|
1200
|
+
existing.mcpServers[id] = desiredEntry;
|
|
1151
1201
|
}
|
|
1202
|
+
reconcileStaleManagedEntries(existing.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env) });
|
|
1152
1203
|
|
|
1153
1204
|
if (DRY_RUN) return;
|
|
1154
|
-
mkdirp(path.dirname(
|
|
1155
|
-
writeFile(
|
|
1205
|
+
mkdirp(path.dirname(mcpJsonPath));
|
|
1206
|
+
writeFile(mcpJsonPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1156
1207
|
}
|
|
1157
1208
|
|
|
1158
1209
|
function syncClaude(entries, targetDir = null, wants = true) {
|
|
@@ -1183,6 +1234,7 @@ function syncClaude(entries, targetDir = null, wants = true) {
|
|
|
1183
1234
|
|
|
1184
1235
|
if (targetDir) {
|
|
1185
1236
|
writeProjectClaudeSettings(targetDir);
|
|
1237
|
+
writeProjectMcpJson(targetDir);
|
|
1186
1238
|
return;
|
|
1187
1239
|
}
|
|
1188
1240
|
|
|
@@ -1200,7 +1252,7 @@ ${personaList}
|
|
|
1200
1252
|
// User-managed file with our managed-block carved out — never doc-stamp.
|
|
1201
1253
|
writeFile(claudeMdPath, replaceManagedBlock(existing, note, mdManagedStart, mdManagedEnd), { stamp: false });
|
|
1202
1254
|
|
|
1203
|
-
// Sync
|
|
1255
|
+
// Sync hooks into ~/.claude/settings.json if it exists
|
|
1204
1256
|
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
1205
1257
|
if (fs.existsSync(claudeSettingsPath)) {
|
|
1206
1258
|
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8"));
|
|
@@ -1220,10 +1272,22 @@ ${personaList}
|
|
|
1220
1272
|
settings.hooks = JSON.parse(hookStr);
|
|
1221
1273
|
}
|
|
1222
1274
|
}
|
|
1223
|
-
const registryMcp = scopedManagedMcpDefs({ projectScope: false });
|
|
1224
|
-
syncGlobalClaudeMcpServers(settings, registryMcp);
|
|
1225
1275
|
if (!DRY_RUN) fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1226
1276
|
}
|
|
1277
|
+
|
|
1278
|
+
// Sync user-scope MCP servers into ~/.claude.json's top-level `mcpServers`
|
|
1279
|
+
// — Claude Code does not read MCP server definitions from settings.json at
|
|
1280
|
+
// any scope (confirmed against code.claude.com/docs/en/mcp and live `claude
|
|
1281
|
+
// mcp list` repro, construct-ranh). Gated on the file already existing, same
|
|
1282
|
+
// as the hooks sync above: only write into Claude Code's own state file for
|
|
1283
|
+
// a user who has actually run the CLI at least once.
|
|
1284
|
+
const claudeUserConfigPath = path.join(home, ".claude.json");
|
|
1285
|
+
if (fs.existsSync(claudeUserConfigPath)) {
|
|
1286
|
+
const claudeUserConfig = JSON.parse(fs.readFileSync(claudeUserConfigPath, "utf8"));
|
|
1287
|
+
const registryMcp = scopedManagedMcpDefs({ projectScope: false });
|
|
1288
|
+
syncGlobalClaudeMcpServers(claudeUserConfig, registryMcp);
|
|
1289
|
+
if (!DRY_RUN) fs.writeFileSync(claudeUserConfigPath, JSON.stringify(claudeUserConfig, null, 2) + "\n");
|
|
1290
|
+
}
|
|
1227
1291
|
}
|
|
1228
1292
|
}
|
|
1229
1293
|
|
|
@@ -1368,15 +1432,14 @@ When using this prompt, stay within the role above and adapt to the current repo
|
|
|
1368
1432
|
// recognized here, so the orchestrator needs its own VS Code agent or it lists
|
|
1369
1433
|
// in the picker with no usable tools.
|
|
1370
1434
|
|
|
1371
|
-
const COPILOT_AGENT_TOOLS = [
|
|
1372
|
-
"construct-mcp
|
|
1373
|
-
"
|
|
1374
|
-
"
|
|
1435
|
+
export const COPILOT_AGENT_TOOLS = [
|
|
1436
|
+
"construct-mcp/orchestration_policy",
|
|
1437
|
+
"construct-mcp/orchestration_run",
|
|
1438
|
+
"construct-mcp/orchestration_readiness",
|
|
1375
1439
|
"search/codebase",
|
|
1376
1440
|
"search/usages",
|
|
1377
1441
|
"search/fileSearch",
|
|
1378
1442
|
"read/problems",
|
|
1379
|
-
"edit/editFiles",
|
|
1380
1443
|
];
|
|
1381
1444
|
|
|
1382
1445
|
function copilotAgentFile(entry, allEntries) {
|
|
@@ -1486,6 +1549,86 @@ function getVSCodeUserMcpPaths() {
|
|
|
1486
1549
|
.filter((file) => fs.existsSync(file));
|
|
1487
1550
|
}
|
|
1488
1551
|
|
|
1552
|
+
// One comparator every host dialect (global Claude, project Claude, VS Code,
|
|
1553
|
+
// Cursor, OpenCode) calls instead of a bespoke preserve/refresh predicate per
|
|
1554
|
+
// surface — so the construct-mcp telemetry env passthrough and a pinned package
|
|
1555
|
+
// version converge identically everywhere rather than drifting per host.
|
|
1556
|
+
// needsRefresh() is the one question every dialect asks: does this host's
|
|
1557
|
+
// existing entry still match what the registry wants, across placeholder
|
|
1558
|
+
// resolution, transport, registry-declared env keys, pinned package versions,
|
|
1559
|
+
// and toolkit path. Callers
|
|
1560
|
+
// build the desired entry with their own host-specific builder (buildClaudeMcpEntry
|
|
1561
|
+
// / buildOpenCodeMcpEntry) first — both a Claude-shape entry (`args`/`env`) and an
|
|
1562
|
+
// OpenCode-shape entry (`command` array with the binary as element 0 / `environment`)
|
|
1563
|
+
// are normalized here so one comparator serves both shapes.
|
|
1564
|
+
|
|
1565
|
+
function entryArgs(entry) {
|
|
1566
|
+
if (Array.isArray(entry?.args)) return entry.args;
|
|
1567
|
+
if (Array.isArray(entry?.command)) return entry.command;
|
|
1568
|
+
return [];
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function entryEnv(entry) {
|
|
1572
|
+
return entry?.env ?? entry?.environment ?? {};
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function entryIsRemote(entry) {
|
|
1576
|
+
return entry?.type === "http" || entry?.type === "remote";
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// A registry env block that declares a key (e.g. the construct-mcp telemetry
|
|
1580
|
+
// passthrough) must be present on the host entry by name; the host-resolved
|
|
1581
|
+
// value differs per host (a literal, a `${VAR}`, a `{env:VAR}`) so only key
|
|
1582
|
+
// presence is compared. Absence — including "no env block at all" — means stale.
|
|
1583
|
+
|
|
1584
|
+
function envKeysMissing(existingEnv, desiredEnv) {
|
|
1585
|
+
const desiredKeys = Object.keys(desiredEnv ?? {});
|
|
1586
|
+
if (desiredKeys.length === 0) return false;
|
|
1587
|
+
const existingKeys = new Set(Object.keys(existingEnv ?? {}));
|
|
1588
|
+
return desiredKeys.some((key) => !existingKeys.has(key));
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
// A version-pinned package arg (`@scope/name@version`, including the `@latest`
|
|
1592
|
+
// anti-pin) is compared as an exact string: if the registry's desired pin string
|
|
1593
|
+
// is not present verbatim anywhere in the existing args/command, the host is
|
|
1594
|
+
// carrying a different (or floating) version and must be refreshed. Unversioned
|
|
1595
|
+
// packages are not compared — pinning is opt-in per package via the catalog.
|
|
1596
|
+
|
|
1597
|
+
function argsVersionDiffers(existingArgs, desiredArgs) {
|
|
1598
|
+
const isPinned = (arg) => typeof arg === "string" && /^@[^/]+\/[^@]+@[\w.-]+$/.test(arg);
|
|
1599
|
+
const desiredPins = (desiredArgs ?? []).filter(isPinned);
|
|
1600
|
+
if (desiredPins.length === 0) return false;
|
|
1601
|
+
const existingPins = new Set((existingArgs ?? []).filter(isPinned));
|
|
1602
|
+
return desiredPins.some((pin) => !existingPins.has(pin));
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
export function needsRefresh(existingEntry, desiredEntry, { root } = {}) {
|
|
1606
|
+
if (!existingEntry) return true;
|
|
1607
|
+
if (JSON.stringify(existingEntry).includes("__")) return true;
|
|
1608
|
+
if (!entryIsRemote(desiredEntry) && entryIsRemote(existingEntry)) return true;
|
|
1609
|
+
if (envKeysMissing(entryEnv(existingEntry), entryEnv(desiredEntry))) return true;
|
|
1610
|
+
if (argsVersionDiffers(entryArgs(existingEntry), entryArgs(desiredEntry))) return true;
|
|
1611
|
+
if (root && mcpEntryPointsOutsideToolkit({ args: entryArgs(existingEntry) }, root)) return true;
|
|
1612
|
+
if (desiredEntry?.cwd !== undefined && existingEntry.cwd !== desiredEntry.cwd) return true;
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
// VS Code's orchestration tools default their working directory to the SERVER
|
|
1617
|
+
// process's cwd, which is host-launch-dependent (construct-6y6w.9) — the same
|
|
1618
|
+
// tool call can read/write a different project depending on which host
|
|
1619
|
+
// launched the server and from where. VS Code resolves `${workspaceFolder}`
|
|
1620
|
+
// against the workspace that owns the `.vscode/mcp.json` the entry lives in,
|
|
1621
|
+
// so pinning it there removes the ambiguity for exactly the host that has a
|
|
1622
|
+
// single fixed workspace folder per config file. A remote/http entry has no
|
|
1623
|
+
// process cwd to pin and is left untouched.
|
|
1624
|
+
|
|
1625
|
+
const VSCODE_WORKSPACE_CWD = "${workspaceFolder}";
|
|
1626
|
+
|
|
1627
|
+
function withVscodeWorkspaceCwd(entry) {
|
|
1628
|
+
if (!entry || entry.type === "http" || entry.type === "remote") return entry;
|
|
1629
|
+
return { ...entry, cwd: VSCODE_WORKSPACE_CWD };
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1489
1632
|
// A merged mcp.json preserves existing entries so user customizations survive a
|
|
1490
1633
|
// re-sync. A construct-owned server path is a fully-resolved, non-placeholder
|
|
1491
1634
|
// path, so the preserve rule keeps it even when it points at a different toolkit
|
|
@@ -1493,34 +1636,119 @@ function getVSCodeUserMcpPaths() {
|
|
|
1493
1636
|
// exist. Treat a construct toolkit path outside the current root as stale so the
|
|
1494
1637
|
// sync refreshes it; user-owned servers carry no lib/mcp toolkit path and stay.
|
|
1495
1638
|
|
|
1639
|
+
// Claude/VS Code/Cursor entries carry the script path in `args`; OpenCode's local
|
|
1640
|
+
// entry shape (buildOpenCodeMcpEntry) instead carries `command: [bin, ...args]`.
|
|
1641
|
+
// Falling back to `command` when `args` is absent lets this one predicate serve
|
|
1642
|
+
// both shapes without OpenCode's caller having to reshape the entry first.
|
|
1643
|
+
|
|
1496
1644
|
export function mcpEntryPointsOutsideToolkit(entry, root) {
|
|
1497
|
-
const args = Array.isArray(entry?.args) ? entry.args : [];
|
|
1498
|
-
return args.some(
|
|
1499
|
-
(
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1645
|
+
const args = Array.isArray(entry?.args) ? entry.args : (Array.isArray(entry?.command) ? entry.command : []);
|
|
1646
|
+
return args.some((arg) => {
|
|
1647
|
+
if (typeof arg !== "string") return false;
|
|
1648
|
+
const normalArg = arg.replace(/\\/g, "/");
|
|
1649
|
+
const normalRoot = root.replace(/\\/g, "/");
|
|
1650
|
+
return /\/lib\/mcp\/[a-z0-9-]+\.mjs$/i.test(normalArg)
|
|
1651
|
+
&& !normalArg.startsWith(`${normalRoot}/`);
|
|
1652
|
+
});
|
|
1503
1653
|
}
|
|
1504
1654
|
|
|
1505
|
-
//
|
|
1506
|
-
//
|
|
1507
|
-
//
|
|
1508
|
-
//
|
|
1509
|
-
//
|
|
1510
|
-
//
|
|
1511
|
-
//
|
|
1512
|
-
|
|
1655
|
+
// The desired-set loop only visits registryMcp ids, so a construct-managed entry
|
|
1656
|
+
// present in the host config but OUTSIDE the current sync set (an optional server
|
|
1657
|
+
// like `memory` the user opted into) is never revisited — a stale toolkit path in it
|
|
1658
|
+
// becomes immortal. This second pass rewrites those in place: the double guard (id is
|
|
1659
|
+
// construct-managed AND its path points outside the toolkit) never touches an
|
|
1660
|
+
// unmanaged/user entry, and rewriting to a path under root makes it idempotent (a
|
|
1661
|
+
// second run finds nothing stale). Rewrite, never delete, never seed — opt-in sticks.
|
|
1662
|
+
|
|
1663
|
+
// A memory entry can be stale without pointing outside the toolkit: pinned to the dead
|
|
1664
|
+
// legacy port, to a port other than the one currently allocated, or to a bridge script
|
|
1665
|
+
// path that does not exist (an old checkout). Any of these means the entry cannot reach
|
|
1666
|
+
// the running memory server — the split-brain — so it must be rewritten to the current
|
|
1667
|
+
// port and path.
|
|
1668
|
+
|
|
1669
|
+
function memoryEntryIsStale(entry) {
|
|
1670
|
+
if (!entry) return false;
|
|
1671
|
+
const want = String(memoryPort(process.env));
|
|
1672
|
+
const portOf = (s) => String(s || "").match(/:(\d+)\/?$/)?.[1];
|
|
1673
|
+
const bridgeUrlEnv = entry.env?.CONSTRUCT_MEMORY_BRIDGE_URL ?? entry.environment?.CONSTRUCT_MEMORY_BRIDGE_URL;
|
|
1674
|
+
const ports = [portOf(entry.url), portOf(bridgeUrlEnv)];
|
|
1675
|
+
const portStale = ports.some((p) => p && p !== want);
|
|
1676
|
+
const commandArgs = Array.isArray(entry.args) ? entry.args : (Array.isArray(entry.command) ? entry.command : []);
|
|
1677
|
+
const scriptArg = commandArgs.find((a) => typeof a === "string" && a.endsWith("memory-bridge.mjs"));
|
|
1678
|
+
const pathMissing = scriptArg ? !fs.existsSync(scriptArg) : false;
|
|
1679
|
+
return portStale || pathMissing;
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
// OpenCode keys config.mcp by getOpenCodeMcpId(id), not the catalog id (identity
|
|
1683
|
+
// today, but the indirection exists for a future per-host rename) — so the host
|
|
1684
|
+
// config key this loop sees is not always the id `registryMcp`/`managedMcpDefs()`
|
|
1685
|
+
// know. `mapId` translates the host key back to the catalog id for the ownership
|
|
1686
|
+
// and sync-set checks; the write-back stays keyed on the original host key so no
|
|
1687
|
+
// entry is renamed, only rewritten in place.
|
|
1688
|
+
|
|
1689
|
+
export function reconcileStaleManagedEntries(configMap, { registryMcp, rebuildEntry, mapId = (key) => key }) {
|
|
1690
|
+
if (!configMap) return false;
|
|
1691
|
+
const managed = managedMcpDefs();
|
|
1692
|
+
let changed = false;
|
|
1693
|
+
for (const [key, entry] of Object.entries(configMap)) {
|
|
1694
|
+
const catalogId = mapId(key);
|
|
1695
|
+
if (catalogId in registryMcp) continue;
|
|
1696
|
+
const mcpDef = managed[catalogId];
|
|
1697
|
+
if (!mcpDef) continue;
|
|
1698
|
+
const stale = mcpEntryPointsOutsideToolkit(entry, root) || (catalogId === "memory" && memoryEntryIsStale(entry));
|
|
1699
|
+
if (!stale) continue;
|
|
1700
|
+
configMap[key] = rebuildEntry(catalogId, mcpDef);
|
|
1701
|
+
changed = true;
|
|
1702
|
+
}
|
|
1703
|
+
return changed;
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
// Workspace defaults Construct manages for VS Code chat. `chat.agentFilesLocations`
|
|
1707
|
+
// pins the agent scan to `.github/agents` so the orchestrator does not list twice
|
|
1708
|
+
// (VS Code also scans `.claude/agents`, whose Claude tool names it ignores); its
|
|
1709
|
+
// power over that built-in compatibility scan is version-dependent, so it is a
|
|
1710
|
+
// best-effort hint. `chat.mcp.autostart` (VS Code ≥1.105, string enum) set to
|
|
1711
|
+
// `always` eager-starts MCP servers so `construct-mcp` is live without a manual
|
|
1712
|
+
// Start each session — the orchestrator's first move is an MCP call, so a dormant
|
|
1713
|
+
// server otherwise reads as "enable the MCP server". Neither removes the one-time
|
|
1714
|
+
// per-developer MCP trust grant, which VS Code stores locally, not in committed
|
|
1715
|
+
// config. Each key is applied only when unset, and a settings.json that is not
|
|
1716
|
+
// strict JSON (commented/JSONC or user-customized) is left untouched.
|
|
1717
|
+
|
|
1718
|
+
const VSCODE_MANAGED_SETTINGS = {
|
|
1719
|
+
"chat.agentFilesLocations": { ".github/agents": true, ".claude/agents": false },
|
|
1720
|
+
"chat.mcp.autoStart": "always",
|
|
1721
|
+
};
|
|
1722
|
+
|
|
1723
|
+
// Strip full-line JSONC comments (`// …`) and trailing commas before JSON.parse.
|
|
1724
|
+
// Handles the common VS Code settings.json patterns (line comments, trailing commas);
|
|
1725
|
+
// does not attempt to handle inline comments after values.
|
|
1726
|
+
|
|
1727
|
+
function parseJsoncContent(text) {
|
|
1728
|
+
const stripped = text
|
|
1729
|
+
.split('\n')
|
|
1730
|
+
.map((line) => {
|
|
1731
|
+
const t = line.trimStart();
|
|
1732
|
+
return t.startsWith('//') ? '' : line;
|
|
1733
|
+
})
|
|
1734
|
+
.join('\n')
|
|
1735
|
+
.replace(/,(\s*[}\]])/g, '$1');
|
|
1736
|
+
return JSON.parse(stripped);
|
|
1737
|
+
}
|
|
1513
1738
|
|
|
1514
|
-
export function
|
|
1739
|
+
export function pinVscodeChatSettings(targetDir) {
|
|
1515
1740
|
if (DRY_RUN) return;
|
|
1516
1741
|
const settingsPath = path.join(targetDir, ".vscode", "settings.json");
|
|
1517
1742
|
let settings = {};
|
|
1518
1743
|
if (fs.existsSync(settingsPath)) {
|
|
1519
|
-
try { settings =
|
|
1744
|
+
try { settings = parseJsoncContent(fs.readFileSync(settingsPath, "utf8")) || {}; }
|
|
1520
1745
|
catch { return; }
|
|
1521
1746
|
}
|
|
1522
|
-
|
|
1523
|
-
|
|
1747
|
+
let changed = false;
|
|
1748
|
+
for (const [key, value] of Object.entries(VSCODE_MANAGED_SETTINGS)) {
|
|
1749
|
+
if (settings[key] === undefined) { settings[key] = value; changed = true; }
|
|
1750
|
+
}
|
|
1751
|
+
if (!changed) return;
|
|
1524
1752
|
mkdirp(path.dirname(settingsPath));
|
|
1525
1753
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1526
1754
|
}
|
|
@@ -1568,20 +1796,16 @@ function syncVSCode(targetDir = null, wants = true) {
|
|
|
1568
1796
|
if (!config.servers) config.servers = {};
|
|
1569
1797
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1570
1798
|
const existingEntry = config.servers[id];
|
|
1571
|
-
const
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1575
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1576
|
-
const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
|
|
1577
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
|
|
1578
|
-
config.servers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
|
|
1799
|
+
const desiredEntry = withVscodeWorkspaceCwd(buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" }));
|
|
1800
|
+
if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
|
|
1801
|
+
config.servers[id] = desiredEntry;
|
|
1579
1802
|
}
|
|
1803
|
+
reconcileStaleManagedEntries(config.servers, { registryMcp, rebuildEntry: (id, def) => withVscodeWorkspaceCwd(buildClaudeMcpEntry(id, def, process.env, { host: "vscode" })) });
|
|
1580
1804
|
if (!DRY_RUN) {
|
|
1581
1805
|
mkdirp(path.dirname(mcpPath));
|
|
1582
1806
|
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1583
1807
|
}
|
|
1584
|
-
|
|
1808
|
+
pinVscodeChatSettings(targetDir);
|
|
1585
1809
|
return true;
|
|
1586
1810
|
}
|
|
1587
1811
|
|
|
@@ -1594,14 +1818,9 @@ function syncVSCode(targetDir = null, wants = true) {
|
|
|
1594
1818
|
if (!config.servers) config.servers = {};
|
|
1595
1819
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1596
1820
|
const existingEntry = config.servers[id];
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1601
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1602
|
-
const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
|
|
1603
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
|
|
1604
|
-
config.servers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
|
|
1821
|
+
const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
|
|
1822
|
+
if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
|
|
1823
|
+
config.servers[id] = desiredEntry;
|
|
1605
1824
|
}
|
|
1606
1825
|
if (!DRY_RUN) fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1607
1826
|
synced = true;
|
|
@@ -1665,14 +1884,11 @@ function syncCursor(targetDir = null, wants = true) {
|
|
|
1665
1884
|
if (!config.mcpServers) config.mcpServers = {};
|
|
1666
1885
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1667
1886
|
const existingEntry = config.mcpServers[id];
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1672
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1673
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch) continue;
|
|
1674
|
-
config.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
|
|
1887
|
+
const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
|
|
1888
|
+
if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
|
|
1889
|
+
config.mcpServers[id] = desiredEntry;
|
|
1675
1890
|
}
|
|
1891
|
+
reconcileStaleManagedEntries(config.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env, { host: "vscode" }) });
|
|
1676
1892
|
if (!DRY_RUN) {
|
|
1677
1893
|
mkdirp(path.dirname(cursorMcpPath));
|
|
1678
1894
|
fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
@@ -1944,18 +2160,32 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
1944
2160
|
// any remote/http memory entry must be rewritten when the registry
|
|
1945
2161
|
// defines a command-based bridge.
|
|
1946
2162
|
const existingEntry = config.mcp[openCodeId];
|
|
1947
|
-
const
|
|
1948
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'remote' || existingEntry.type === 'http');
|
|
1949
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
2163
|
+
const desiredEntry = buildOpenCodeMcpEntry(id, mcpDef, process.env).entry;
|
|
1950
2164
|
|
|
1951
|
-
|
|
1952
|
-
|
|
2165
|
+
// The registry's own args can carry an unresolved `__NAME__` template (a
|
|
2166
|
+
// secret var not yet set in this environment); needsRefresh only inspects
|
|
2167
|
+
// built entries, so a still-templated registry def is checked separately
|
|
2168
|
+
// and always wins a refresh once the var resolves.
|
|
1953
2169
|
const argsHaveTemplates = (mcpDef.args ?? []).some((a) => typeof a === 'string' && a.includes('__'));
|
|
1954
|
-
if (
|
|
1955
|
-
config.mcp[openCodeId] =
|
|
2170
|
+
if (argsHaveTemplates || needsRefresh(existingEntry, desiredEntry, { root })) {
|
|
2171
|
+
config.mcp[openCodeId] = desiredEntry;
|
|
1956
2172
|
}
|
|
1957
2173
|
}
|
|
1958
2174
|
|
|
2175
|
+
// The sync-set loop above only visits `registryMcp` ids, so a managed-but-optional
|
|
2176
|
+
// entry (e.g. `memory` in project scope) with a stale toolkit path is never
|
|
2177
|
+
// revisited here either — the same immortal-entry gap fixed for VS Code/Cursor/
|
|
2178
|
+
// Claude project (construct-6y6w.1). `getOpenCodeMcpId` keys config.mcp by the
|
|
2179
|
+
// host id rather than the catalog id, so map it back before checking ownership.
|
|
2180
|
+
const catalogIdForOpenCodeKey = new Map(
|
|
2181
|
+
Object.keys(managedMcpDefs()).map((catalogId) => [getOpenCodeMcpId(catalogId), catalogId]),
|
|
2182
|
+
);
|
|
2183
|
+
reconcileStaleManagedEntries(config.mcp, {
|
|
2184
|
+
registryMcp,
|
|
2185
|
+
mapId: (key) => catalogIdForOpenCodeKey.get(key) ?? key,
|
|
2186
|
+
rebuildEntry: (catalogId, def) => buildOpenCodeMcpEntry(catalogId, def, process.env).entry,
|
|
2187
|
+
});
|
|
2188
|
+
|
|
1959
2189
|
// Heavy external MCP servers serialize a measured ~37k tokens of tool schema
|
|
1960
2190
|
// into EVERY agent's request — github ~30k alone (fixtures 2026-06-22) —
|
|
1961
2191
|
// agent's request — including the built-in Build/Plan agents the per-agent
|
|
@@ -2033,7 +2263,7 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
2033
2263
|
};
|
|
2034
2264
|
}
|
|
2035
2265
|
|
|
2036
|
-
// Hybrid split (
|
|
2266
|
+
// Hybrid split (cheap local editor, capable architect). When the fast tier is a LOCAL model, emit a
|
|
2037
2267
|
// narrow `construct-local` editor: it does bounded edits on a cheap local model and hands
|
|
2038
2268
|
// planning/reasoning back to `construct` (the architect, which stays on the user's chosen
|
|
2039
2269
|
// model — we never pin it). The editor's model is NOT the generic fast-tier default
|
|
@@ -2096,17 +2326,17 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
2096
2326
|
delete config.model;
|
|
2097
2327
|
}
|
|
2098
2328
|
|
|
2099
|
-
// OpenCode's built-in helper agents
|
|
2100
|
-
//
|
|
2101
|
-
//
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2329
|
+
// OpenCode's built-in helper agents (session naming, summaries, compaction)
|
|
2330
|
+
// must follow the host's live routing — an absolute model pin here made
|
|
2331
|
+
// compaction call a provider the user had no key/credits for, failing hard
|
|
2332
|
+
// even though a working model was selected for the session. Keep the entries
|
|
2333
|
+
// present (hosts and tests rely on them existing) but strip any model pin a
|
|
2334
|
+
// previous sync wrote, whether the old weak coder default or a strong tier
|
|
2335
|
+
// pin, so OpenCode falls back to its own session-model/small_model routing.
|
|
2336
|
+
for (const key of ["title", "summary", "compaction"]) {
|
|
2337
|
+
const existing = config.agent[key] && typeof config.agent[key] === "object" ? config.agent[key] : {};
|
|
2338
|
+
delete existing.model;
|
|
2339
|
+
config.agent[key] = existing;
|
|
2110
2340
|
}
|
|
2111
2341
|
|
|
2112
2342
|
writeOpenCodeConfig(config, configPath);
|