@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
|
@@ -114,18 +114,6 @@
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
},
|
|
117
|
-
"hosts": {
|
|
118
|
-
"type": "object",
|
|
119
|
-
"description": "Granular control over editor and platform adapter sets.",
|
|
120
|
-
"properties": {
|
|
121
|
-
"claude": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
|
122
|
-
"opencode": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
|
123
|
-
"codex": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
|
124
|
-
"copilot": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
|
125
|
-
"vscode": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
|
|
126
|
-
"cursor": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }
|
|
127
|
-
}
|
|
128
|
-
},
|
|
129
117
|
"providers": {
|
|
130
118
|
"type": "object",
|
|
131
119
|
"description": "Provider-specific settings (e.g., Anthropic, OpenAI, GitHub). Values starting with '$' are interpolated from environment variables.",
|
|
@@ -367,10 +367,8 @@
|
|
|
367
367
|
},
|
|
368
368
|
"postconditions": {
|
|
369
369
|
"type": "array",
|
|
370
|
-
"description": "Conditions that must be true after this contract completes.",
|
|
371
|
-
"items": {
|
|
372
|
-
"type": "string"
|
|
373
|
-
}
|
|
370
|
+
"description": "Conditions that must be true after this contract completes. Each entry is a structured object with a required `postconditionType` of 'executable' (carries a `check` — or an `enforcedVia` pointer to lib/specialists/postconditions.mjs / contract.output — that lib/contracts/validate.mjs actually runs) or 'advisory' (documented, not mechanically checked). Bare prose strings are a legacy, deprecated shape.",
|
|
371
|
+
"items": {}
|
|
374
372
|
},
|
|
375
373
|
"teamBoundary": {
|
|
376
374
|
"type": "object",
|
|
@@ -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";
|
|
@@ -453,7 +459,7 @@ const standardConstructTools = [
|
|
|
453
459
|
// agent's tools list or the call is blocked, leaving the orchestrator unable to route.
|
|
454
460
|
// Single source of truth so every host's orchestrator grant stays in parity.
|
|
455
461
|
|
|
456
|
-
const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run"];
|
|
462
|
+
const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run", "orchestration_delegation_next"];
|
|
457
463
|
const managedStart = `# BEGIN ${systemName.toUpperCase()} AGENTS`;
|
|
458
464
|
const managedEnd = `# END ${systemName.toUpperCase()} AGENTS`;
|
|
459
465
|
const mdManagedStart = `<!-- BEGIN ${systemName.toUpperCase()} AGENTS -->`;
|
|
@@ -485,10 +491,17 @@ function resolveArgs(args) {
|
|
|
485
491
|
: a));
|
|
486
492
|
}
|
|
487
493
|
|
|
488
|
-
|
|
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) {
|
|
489
503
|
if (typeof value === "string") {
|
|
490
504
|
return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => {
|
|
491
|
-
if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
|
|
492
505
|
if (name === "CX_TOOLKIT_DIR") return root;
|
|
493
506
|
if (name === "MEMORY_PORT") return process.env.MEMORY_PORT || "8765";
|
|
494
507
|
if (name === "CONSTRUCT_MEMORY_BRIDGE_URL") {
|
|
@@ -500,6 +513,7 @@ function resolveTemplateStrings(value) {
|
|
|
500
513
|
if (/(?:TOKEN|SECRET|API_KEY|PUBLIC_KEY|PRIVATE_KEY)$/.test(name)) {
|
|
501
514
|
return `{env:${name}}`;
|
|
502
515
|
}
|
|
516
|
+
if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
|
|
503
517
|
return `__${name}__`;
|
|
504
518
|
});
|
|
505
519
|
}
|
|
@@ -778,7 +792,7 @@ function orchestrationMicroPrompt(platform) {
|
|
|
778
792
|
|
|
779
793
|
// Directive for the local editor agent (construct-local). It executes bounded work on
|
|
780
794
|
// the cheap local model and hands planning/reasoning back to the construct architect —
|
|
781
|
-
//
|
|
795
|
+
// a cheap-editor/capable-architect split. Kept short: a small model must actually obey it.
|
|
782
796
|
|
|
783
797
|
const LOCAL_EDITOR_DIRECTIVE =
|
|
784
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` +
|
|
@@ -1016,7 +1030,7 @@ ${buildPrompt(entry, allEntries, "claude")}
|
|
|
1016
1030
|
* Rewrite the home-mode hook command pattern
|
|
1017
1031
|
* node "$HOME/.config/construct/lib/hooks/<name>.mjs"
|
|
1018
1032
|
* into the project-portable form
|
|
1019
|
-
* node "${CLAUDE_PROJECT_DIR
|
|
1033
|
+
* node "${CLAUDE_PROJECT_DIR:-<absRoot>}/.construct/run.mjs" hook <name>
|
|
1020
1034
|
* so the resulting settings.json works on any clone where the project ships
|
|
1021
1035
|
* the .construct/ launcher (committed by `npm install`'s postinstall or by
|
|
1022
1036
|
* `construct init`). The launcher resolves Construct via node_modules → npx
|
|
@@ -1024,21 +1038,29 @@ ${buildPrompt(entry, allEntries, "claude")}
|
|
|
1024
1038
|
* works for non-Node ecosystems too. Other commands (inline node -e
|
|
1025
1039
|
* snippets, npx block-no-verify@…) are left untouched.
|
|
1026
1040
|
*
|
|
1027
|
-
* The `${CLAUDE_PROJECT_DIR
|
|
1041
|
+
* The `${CLAUDE_PROJECT_DIR:-<absRoot>}` anchor matters: hosts invoke hooks with
|
|
1028
1042
|
* a working directory that is not guaranteed to be the project root (observed:
|
|
1029
|
-
* $HOME), and a bare relative `.construct/run.mjs` then
|
|
1030
|
-
* at node:internal/modules/cjs/loader
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
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.
|
|
1033
1053
|
*/
|
|
1034
|
-
function makeHooksPortable(hooksJson) {
|
|
1054
|
+
function makeHooksPortable(hooksJson, projectRoot) {
|
|
1055
|
+
const anchor = `\${CLAUDE_PROJECT_DIR:-${path.resolve(projectRoot)}}`;
|
|
1056
|
+
|
|
1035
1057
|
// Operate on the in-memory object so we don't fight JSON string escaping.
|
|
1036
1058
|
const replaceCommand = (cmd) => {
|
|
1037
1059
|
if (typeof cmd !== 'string') return cmd;
|
|
1038
1060
|
const m = cmd.match(/^node\s+"?\$HOME\/\.config\/construct\/lib\/hooks\/([a-z0-9-]+)\.mjs"?\s*(.*)$/);
|
|
1039
1061
|
if (!m) return cmd;
|
|
1040
1062
|
const [, name, rest] = m;
|
|
1041
|
-
return `node "
|
|
1063
|
+
return `node "${anchor}/.construct/run.mjs" hook ${name}${rest ? ' ' + rest.trim() : ''}`;
|
|
1042
1064
|
};
|
|
1043
1065
|
|
|
1044
1066
|
const walk = (node) => {
|
|
@@ -1091,14 +1113,9 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
|
|
|
1091
1113
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1092
1114
|
if (!GLOBAL_CLAUDE_MCP_IDS.has(id)) continue;
|
|
1093
1115
|
const existingEntry = settings.mcpServers[id];
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
|
|
1098
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1099
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1100
|
-
if (existingEntry && !hasPlaceholder && !hasFloatingVersion && !transportMismatch) continue;
|
|
1101
|
-
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;
|
|
1102
1119
|
}
|
|
1103
1120
|
}
|
|
1104
1121
|
|
|
@@ -1107,6 +1124,12 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
|
|
|
1107
1124
|
* with hook commands rewritten to be path-relative to whatever Construct
|
|
1108
1125
|
* install the project carries. Merges into an existing settings.json
|
|
1109
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`.
|
|
1110
1133
|
*/
|
|
1111
1134
|
function writeProjectClaudeSettings(targetDir) {
|
|
1112
1135
|
const settingsPath = path.join(targetDir, ".claude", "settings.json");
|
|
@@ -1120,17 +1143,42 @@ function writeProjectClaudeSettings(targetDir) {
|
|
|
1120
1143
|
: {};
|
|
1121
1144
|
|
|
1122
1145
|
if (template.hooks) {
|
|
1123
|
-
existing.hooks = JSON.parse(makeHooksPortable(template.hooks));
|
|
1146
|
+
existing.hooks = JSON.parse(makeHooksPortable(template.hooks, targetDir));
|
|
1124
1147
|
}
|
|
1125
1148
|
if (template.permissions) {
|
|
1126
1149
|
existing.permissions ??= template.permissions;
|
|
1127
1150
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
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
|
+
}
|
|
1134
1182
|
}
|
|
1135
1183
|
}
|
|
1136
1184
|
|
|
@@ -1139,19 +1187,23 @@ function writeProjectClaudeSettings(targetDir) {
|
|
|
1139
1187
|
// the server exposing project_context/get_skill/get_template/orchestration_policy
|
|
1140
1188
|
// that the specialist loop depends on. The curated template omits it, which left
|
|
1141
1189
|
// Claude Code as the only selected tool without the construct config. Merge the
|
|
1142
|
-
// registry on top of the template seed
|
|
1143
|
-
//
|
|
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.
|
|
1144
1194
|
const registryMcp = scopedManagedMcpDefs({ projectScope: true });
|
|
1145
|
-
existing.mcpServers ??= {};
|
|
1146
1195
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1147
|
-
if (existing.mcpServers[id]) continue;
|
|
1148
1196
|
if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
|
|
1149
|
-
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;
|
|
1150
1201
|
}
|
|
1202
|
+
reconcileStaleManagedEntries(existing.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env) });
|
|
1151
1203
|
|
|
1152
1204
|
if (DRY_RUN) return;
|
|
1153
|
-
mkdirp(path.dirname(
|
|
1154
|
-
writeFile(
|
|
1205
|
+
mkdirp(path.dirname(mcpJsonPath));
|
|
1206
|
+
writeFile(mcpJsonPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1155
1207
|
}
|
|
1156
1208
|
|
|
1157
1209
|
function syncClaude(entries, targetDir = null, wants = true) {
|
|
@@ -1182,6 +1234,7 @@ function syncClaude(entries, targetDir = null, wants = true) {
|
|
|
1182
1234
|
|
|
1183
1235
|
if (targetDir) {
|
|
1184
1236
|
writeProjectClaudeSettings(targetDir);
|
|
1237
|
+
writeProjectMcpJson(targetDir);
|
|
1185
1238
|
return;
|
|
1186
1239
|
}
|
|
1187
1240
|
|
|
@@ -1199,7 +1252,7 @@ ${personaList}
|
|
|
1199
1252
|
// User-managed file with our managed-block carved out — never doc-stamp.
|
|
1200
1253
|
writeFile(claudeMdPath, replaceManagedBlock(existing, note, mdManagedStart, mdManagedEnd), { stamp: false });
|
|
1201
1254
|
|
|
1202
|
-
// Sync
|
|
1255
|
+
// Sync hooks into ~/.claude/settings.json if it exists
|
|
1203
1256
|
const claudeSettingsPath = path.join(home, ".claude", "settings.json");
|
|
1204
1257
|
if (fs.existsSync(claudeSettingsPath)) {
|
|
1205
1258
|
const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8"));
|
|
@@ -1219,10 +1272,22 @@ ${personaList}
|
|
|
1219
1272
|
settings.hooks = JSON.parse(hookStr);
|
|
1220
1273
|
}
|
|
1221
1274
|
}
|
|
1222
|
-
const registryMcp = scopedManagedMcpDefs({ projectScope: false });
|
|
1223
|
-
syncGlobalClaudeMcpServers(settings, registryMcp);
|
|
1224
1275
|
if (!DRY_RUN) fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1225
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
|
+
}
|
|
1226
1291
|
}
|
|
1227
1292
|
}
|
|
1228
1293
|
|
|
@@ -1484,6 +1549,86 @@ function getVSCodeUserMcpPaths() {
|
|
|
1484
1549
|
.filter((file) => fs.existsSync(file));
|
|
1485
1550
|
}
|
|
1486
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
|
+
|
|
1487
1632
|
// A merged mcp.json preserves existing entries so user customizations survive a
|
|
1488
1633
|
// re-sync. A construct-owned server path is a fully-resolved, non-placeholder
|
|
1489
1634
|
// path, so the preserve rule keeps it even when it points at a different toolkit
|
|
@@ -1491,8 +1636,13 @@ function getVSCodeUserMcpPaths() {
|
|
|
1491
1636
|
// exist. Treat a construct toolkit path outside the current root as stale so the
|
|
1492
1637
|
// sync refreshes it; user-owned servers carry no lib/mcp toolkit path and stay.
|
|
1493
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
|
+
|
|
1494
1644
|
export function mcpEntryPointsOutsideToolkit(entry, root) {
|
|
1495
|
-
const args = Array.isArray(entry?.args) ? entry.args : [];
|
|
1645
|
+
const args = Array.isArray(entry?.args) ? entry.args : (Array.isArray(entry?.command) ? entry.command : []);
|
|
1496
1646
|
return args.some((arg) => {
|
|
1497
1647
|
if (typeof arg !== "string") return false;
|
|
1498
1648
|
const normalArg = arg.replace(/\\/g, "/");
|
|
@@ -1502,6 +1652,57 @@ export function mcpEntryPointsOutsideToolkit(entry, root) {
|
|
|
1502
1652
|
});
|
|
1503
1653
|
}
|
|
1504
1654
|
|
|
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
|
+
|
|
1505
1706
|
// Workspace defaults Construct manages for VS Code chat. `chat.agentFilesLocations`
|
|
1506
1707
|
// pins the agent scan to `.github/agents` so the orchestrator does not list twice
|
|
1507
1708
|
// (VS Code also scans `.claude/agents`, whose Claude tool names it ignores); its
|
|
@@ -1595,15 +1796,11 @@ function syncVSCode(targetDir = null, wants = true) {
|
|
|
1595
1796
|
if (!config.servers) config.servers = {};
|
|
1596
1797
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1597
1798
|
const existingEntry = config.servers[id];
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1602
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1603
|
-
const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
|
|
1604
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
|
|
1605
|
-
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;
|
|
1606
1802
|
}
|
|
1803
|
+
reconcileStaleManagedEntries(config.servers, { registryMcp, rebuildEntry: (id, def) => withVscodeWorkspaceCwd(buildClaudeMcpEntry(id, def, process.env, { host: "vscode" })) });
|
|
1607
1804
|
if (!DRY_RUN) {
|
|
1608
1805
|
mkdirp(path.dirname(mcpPath));
|
|
1609
1806
|
fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
@@ -1621,14 +1818,9 @@ function syncVSCode(targetDir = null, wants = true) {
|
|
|
1621
1818
|
if (!config.servers) config.servers = {};
|
|
1622
1819
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1623
1820
|
const existingEntry = config.servers[id];
|
|
1624
|
-
const
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1628
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1629
|
-
const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
|
|
1630
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
|
|
1631
|
-
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;
|
|
1632
1824
|
}
|
|
1633
1825
|
if (!DRY_RUN) fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1634
1826
|
synced = true;
|
|
@@ -1692,14 +1884,11 @@ function syncCursor(targetDir = null, wants = true) {
|
|
|
1692
1884
|
if (!config.mcpServers) config.mcpServers = {};
|
|
1693
1885
|
for (const [id, mcpDef] of Object.entries(registryMcp)) {
|
|
1694
1886
|
const existingEntry = config.mcpServers[id];
|
|
1695
|
-
const
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
|
|
1699
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
1700
|
-
if (existingEntry && !hasPlaceholder && !transportMismatch) continue;
|
|
1701
|
-
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;
|
|
1702
1890
|
}
|
|
1891
|
+
reconcileStaleManagedEntries(config.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env, { host: "vscode" }) });
|
|
1703
1892
|
if (!DRY_RUN) {
|
|
1704
1893
|
mkdirp(path.dirname(cursorMcpPath));
|
|
1705
1894
|
fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
|
|
@@ -1971,18 +2160,32 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
1971
2160
|
// any remote/http memory entry must be rewritten when the registry
|
|
1972
2161
|
// defines a command-based bridge.
|
|
1973
2162
|
const existingEntry = config.mcp[openCodeId];
|
|
1974
|
-
const
|
|
1975
|
-
const existingIsRemote = existingEntry && (existingEntry.type === 'remote' || existingEntry.type === 'http');
|
|
1976
|
-
const transportMismatch = registryWantsCommand && existingIsRemote;
|
|
2163
|
+
const desiredEntry = buildOpenCodeMcpEntry(id, mcpDef, process.env).entry;
|
|
1977
2164
|
|
|
1978
|
-
|
|
1979
|
-
|
|
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.
|
|
1980
2169
|
const argsHaveTemplates = (mcpDef.args ?? []).some((a) => typeof a === 'string' && a.includes('__'));
|
|
1981
|
-
if (
|
|
1982
|
-
config.mcp[openCodeId] =
|
|
2170
|
+
if (argsHaveTemplates || needsRefresh(existingEntry, desiredEntry, { root })) {
|
|
2171
|
+
config.mcp[openCodeId] = desiredEntry;
|
|
1983
2172
|
}
|
|
1984
2173
|
}
|
|
1985
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
|
+
|
|
1986
2189
|
// Heavy external MCP servers serialize a measured ~37k tokens of tool schema
|
|
1987
2190
|
// into EVERY agent's request — github ~30k alone (fixtures 2026-06-22) —
|
|
1988
2191
|
// agent's request — including the built-in Build/Plan agents the per-agent
|
|
@@ -2060,7 +2263,7 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
2060
2263
|
};
|
|
2061
2264
|
}
|
|
2062
2265
|
|
|
2063
|
-
// Hybrid split (
|
|
2266
|
+
// Hybrid split (cheap local editor, capable architect). When the fast tier is a LOCAL model, emit a
|
|
2064
2267
|
// narrow `construct-local` editor: it does bounded edits on a cheap local model and hands
|
|
2065
2268
|
// planning/reasoning back to `construct` (the architect, which stays on the user's chosen
|
|
2066
2269
|
// model — we never pin it). The editor's model is NOT the generic fast-tier default
|
|
@@ -2123,17 +2326,17 @@ function syncOpencode(entries, targetDir = null, wants = true) {
|
|
|
2123
2326
|
delete config.model;
|
|
2124
2327
|
}
|
|
2125
2328
|
|
|
2126
|
-
// OpenCode's built-in helper agents
|
|
2127
|
-
//
|
|
2128
|
-
//
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
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;
|
|
2137
2340
|
}
|
|
2138
2341
|
|
|
2139
2342
|
writeOpenCodeConfig(config, configPath);
|
|
@@ -21,7 +21,7 @@ Use when: an architectural decision is made that affects the system structure, d
|
|
|
21
21
|
2. **Write to `docs/decisions/adr/ADR-{NNN}-{slug}.md`** using the template from `get_template("adr")`: resolves `.cx/templates/docs/adr.md` (override) then `templates/docs/adr.md` (shipped)
|
|
22
22
|
- NNN = next sequential number (check existing files)
|
|
23
23
|
3. **Also write a shorter entry to `.cx/decisions/`** for session context
|
|
24
|
-
4. **cx-
|
|
24
|
+
4. **cx-operations** updates `.cx/context.md` Architecture Notes with a one-line summary and link
|
|
25
25
|
|
|
26
26
|
## File naming
|
|
27
27
|
- `docs/decisions/adr/ADR-001-use-postgres-over-mysql.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs-codebase-research-workflow
|
|
3
|
-
description: "Use when: cx-
|
|
3
|
+
description: "Use when: cx-researcher maps the repo — entry points, dependencies, hot paths, or unfamiliar subsystems."
|
|
4
4
|
inputs: [repository-path]
|
|
5
5
|
artifactType: research-brief
|
|
6
6
|
toneDefault: pedagogical
|
|
@@ -9,7 +9,7 @@ verificationBar: "Every load-bearing claim cites a verifiable primary source; la
|
|
|
9
9
|
---
|
|
10
10
|
# Codebase Research Workflow
|
|
11
11
|
|
|
12
|
-
Use when: cx-
|
|
12
|
+
Use when: cx-researcher investigates **this repository** — structure, dependencies, behavior. Not for external vendor research or user interviews.
|
|
13
13
|
|
|
14
14
|
Call `get_skill("roles/researcher")` and `get_skill("exploration/repo-map")` before deep dives.
|
|
15
15
|
|
|
@@ -37,7 +37,7 @@ Call `get_skill("roles/researcher")` and `get_skill("exploration/repo-map")` bef
|
|
|
37
37
|
|
|
38
38
|
- Every architectural claim traceable to file:line.
|
|
39
39
|
- Unknown paths marked `[unverified]` until read.
|
|
40
|
-
- cx-
|
|
40
|
+
- cx-researcher must **not** answer product prioritization or user preference questions.
|
|
41
41
|
## Release gate
|
|
42
42
|
|
|
43
43
|
Run `construct artifact validate <path> --type=<type>` before marking the artifact approved.
|