@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
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/filter-audit.mjs — per-poll provider filter audit log.
|
|
3
|
+
*
|
|
4
|
+
* ADR-0060 requires every poll to record, per source, the filter that was
|
|
5
|
+
* enforced and how many items it dropped. Appends one JSONL line per source
|
|
6
|
+
* per poll to `<rootDir>/.cx/providers/filter-audit.jsonl` with the
|
|
7
|
+
* acceptance-criteria field names `{provider, instance, filterHash, matched,
|
|
8
|
+
* dropped}`, plus the ADR's own `fetched`/`admitted` and `pushdown` fields
|
|
9
|
+
* for the fuller audit shape. Rotation uses `appendWithRotationSync`
|
|
10
|
+
* directly (not the shared `appendBounded` channel registry) to keep this
|
|
11
|
+
* change scoped to `lib/providers/*`.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
|
|
16
|
+
import { appendWithRotationSync } from '../logging/rotate.mjs';
|
|
17
|
+
|
|
18
|
+
const AUDIT_SUBDIR = ['.cx', 'providers'];
|
|
19
|
+
const AUDIT_FILE = 'filter-audit.jsonl';
|
|
20
|
+
|
|
21
|
+
// 10 MB per shard, 5 shards kept — a filter-audit line is written once per
|
|
22
|
+
// source per poll, so this comfortably covers years of daemon uptime.
|
|
23
|
+
|
|
24
|
+
const MAX_BYTES = 10 * 1024 * 1024;
|
|
25
|
+
const MAX_SEGMENTS = 5;
|
|
26
|
+
|
|
27
|
+
export function filterAuditPath(rootDir) {
|
|
28
|
+
return join(rootDir, ...AUDIT_SUBDIR, AUDIT_FILE);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Append one filter-audit record. `matched`/`dropped` are the
|
|
33
|
+
* acceptance-criteria field names; `fetched`/`admitted` mirror the ADR-0060
|
|
34
|
+
* audit shape (`admitted` === `matched`) so both vocabularies are queryable
|
|
35
|
+
* from the same line without a reader needing to know which one shipped.
|
|
36
|
+
*/
|
|
37
|
+
export function recordFilterAudit(rootDir, {
|
|
38
|
+
provider,
|
|
39
|
+
instance = null,
|
|
40
|
+
filterHash: hash = null,
|
|
41
|
+
scope = null,
|
|
42
|
+
predicates = null,
|
|
43
|
+
nativeQuery = null,
|
|
44
|
+
pushdown = false,
|
|
45
|
+
fetched,
|
|
46
|
+
matched,
|
|
47
|
+
}) {
|
|
48
|
+
const record = {
|
|
49
|
+
ts: new Date().toISOString(),
|
|
50
|
+
provider,
|
|
51
|
+
instance,
|
|
52
|
+
filterHash: hash,
|
|
53
|
+
filterApplied: { scope, predicates, nativeQuery, pushdown, fetched, admitted: matched },
|
|
54
|
+
matched,
|
|
55
|
+
dropped: fetched - matched,
|
|
56
|
+
};
|
|
57
|
+
appendWithRotationSync(filterAuditPath(rootDir), JSON.stringify(record) + '\n', {
|
|
58
|
+
maxBytes: MAX_BYTES,
|
|
59
|
+
maxSegments: MAX_SEGMENTS,
|
|
60
|
+
});
|
|
61
|
+
return record;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function readFilterAudit(rootDir) {
|
|
65
|
+
const path = filterAuditPath(rootDir);
|
|
66
|
+
if (!existsSync(path)) return [];
|
|
67
|
+
return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
68
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/filter-schema.mjs — provider filter block JSON schema (ADR-0060).
|
|
3
|
+
*
|
|
4
|
+
* Ships the normalized filter grammar every provider kind may accept:
|
|
5
|
+
* `scope` (per-kind container allowlist), `predicates` (portable field-level
|
|
6
|
+
* filter core), and `nativeQuery` (provider-native passthrough escape hatch).
|
|
7
|
+
* Per-kind manifest `configSchema` fields reference this via `$ref`; per-kind
|
|
8
|
+
* `SCOPE_FIELDS_BY_PROVIDER` in `lib/providers/contract.mjs` declares which
|
|
9
|
+
* `scope` keys a given provider kind actually honors.
|
|
10
|
+
*
|
|
11
|
+
* Data-only module (schema + enums), consumed by `validateFilterConfig()`
|
|
12
|
+
* in `lib/providers/contract.mjs`. No JSON-Schema engine runs here — the
|
|
13
|
+
* shape below is the source of truth the hand-rolled validator checks
|
|
14
|
+
* against, keeping schema and validator from drifting silently apart.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export const FILTER_SCHEMA = Object.freeze({
|
|
18
|
+
$id: 'construct:provider-filter',
|
|
19
|
+
type: 'object',
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
properties: {
|
|
22
|
+
scope: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
properties: {
|
|
26
|
+
projects: { type: 'array', items: { type: 'string' } },
|
|
27
|
+
repos: { type: 'array', items: { type: 'string' } },
|
|
28
|
+
repoGlob: { type: 'array', items: { type: 'string' } },
|
|
29
|
+
channels: { type: 'array', items: { type: 'string' } },
|
|
30
|
+
spaces: { type: 'array', items: { type: 'string' } },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
predicates: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
properties: {
|
|
37
|
+
assignee: { type: 'array', items: { type: 'string' } },
|
|
38
|
+
statusCategory: { type: 'array', items: { enum: ['to-do', 'in-progress', 'done'] } },
|
|
39
|
+
priority: { type: 'array', items: { type: 'string' } },
|
|
40
|
+
label: { type: 'array', items: { type: 'string' } },
|
|
41
|
+
updatedSince: { type: 'string' },
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
nativeQuery: { type: 'string' },
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
export const SCOPE_KEYS = Object.freeze(['projects', 'repos', 'repoGlob', 'channels', 'spaces']);
|
|
49
|
+
|
|
50
|
+
export const PREDICATE_KEYS = Object.freeze(['assignee', 'statusCategory', 'priority', 'label', 'updatedSince']);
|
|
51
|
+
|
|
52
|
+
export const STATUS_CATEGORIES = Object.freeze(['to-do', 'in-progress', 'done']);
|
|
53
|
+
|
|
54
|
+
export const TOP_LEVEL_KEYS = Object.freeze(['scope', 'predicates', 'nativeQuery']);
|
|
@@ -14,10 +14,21 @@
|
|
|
14
14
|
* The provider is intentionally narrow in v1 — issue + PR + code search +
|
|
15
15
|
* a single repo metadata read. Webhook signature verification is in place
|
|
16
16
|
* so consumers can mount a receiver without re-implementing it.
|
|
17
|
+
*
|
|
18
|
+
* Scope enforcement: when `repoAllowlist` or `repoAllowGlob` is present in
|
|
19
|
+
* config, `read()` validates `config.repo` and `search()` validates every
|
|
20
|
+
* `repo:` qualifier in the query through `validateAllowlist()`
|
|
21
|
+
* (lib/providers/contract.mjs) before any network call, throwing a typed
|
|
22
|
+
* `OUT_OF_SCOPE` error on a blocked target. A search query with no `repo:`
|
|
23
|
+
* qualifier is rejected outright when an allowlist is configured — an
|
|
24
|
+
* unscoped query could otherwise span the whole credential's visible
|
|
25
|
+
* surface and defeat the allowlist.
|
|
17
26
|
*/
|
|
18
27
|
|
|
19
28
|
import crypto from 'node:crypto';
|
|
20
29
|
|
|
30
|
+
import { validateAllowlist } from '../contract.mjs';
|
|
31
|
+
|
|
21
32
|
const API = 'https://api.github.com';
|
|
22
33
|
|
|
23
34
|
function authHeader(env) {
|
|
@@ -25,6 +36,10 @@ function authHeader(env) {
|
|
|
25
36
|
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
26
37
|
}
|
|
27
38
|
|
|
39
|
+
function outOfScopeError(reason) {
|
|
40
|
+
return Object.assign(new Error(reason), { code: 'OUT_OF_SCOPE' });
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
async function ghFetch(pathOrUrl, env, init = {}) {
|
|
29
44
|
const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${API}${pathOrUrl}`;
|
|
30
45
|
const headers = {
|
|
@@ -83,6 +98,8 @@ export function create({ env = process.env } = {}) {
|
|
|
83
98
|
|
|
84
99
|
async read(config) {
|
|
85
100
|
if (!config?.repo) throw new Error('github.read: config.repo required (owner/name)');
|
|
101
|
+
const check = validateAllowlist('github', config.repo, config);
|
|
102
|
+
if (!check.allowed) throw outOfScopeError(check.reason);
|
|
86
103
|
return ghFetch(`/repos/${config.repo}`, env);
|
|
87
104
|
},
|
|
88
105
|
|
|
@@ -90,6 +107,20 @@ export function create({ env = process.env } = {}) {
|
|
|
90
107
|
const kind = config?.kind || 'issues';
|
|
91
108
|
const query = config?.query;
|
|
92
109
|
if (!query) throw new Error('github.search: config.query required');
|
|
110
|
+
|
|
111
|
+
const hasAllowlistConfig = (Array.isArray(config?.repoAllowlist) && config.repoAllowlist.length > 0)
|
|
112
|
+
|| (typeof config?.repoAllowGlob === 'string' && config.repoAllowGlob.length > 0);
|
|
113
|
+
const repoQualifiers = [...query.matchAll(/\brepo:(\S+)/g)].map((m) => m[1]);
|
|
114
|
+
|
|
115
|
+
if (hasAllowlistConfig && repoQualifiers.length === 0) {
|
|
116
|
+
throw outOfScopeError('github.search: repoAllowlist/repoAllowGlob is configured; query must include a repo: qualifier to enforce scope');
|
|
117
|
+
}
|
|
118
|
+
for (const repo of repoQualifiers) {
|
|
119
|
+
const target = repo.includes('/') ? repo.split('/').pop() : repo;
|
|
120
|
+
const check = validateAllowlist('github', target, config);
|
|
121
|
+
if (!check.allowed) throw outOfScopeError(check.reason);
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
const path = kind === 'code'
|
|
94
125
|
? `/search/code?q=${encodeURIComponent(query)}`
|
|
95
126
|
: kind === 'prs'
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/instance-config.mjs — provider instance config persistence + validation.
|
|
3
|
+
*
|
|
4
|
+
* Backs `construct provider add|configure` (LMCP-B12). An "instance config"
|
|
5
|
+
* is the per-project, per-provider-id settings block (Jira project keys,
|
|
6
|
+
* GitHub repo, Slack channel, plus the ADR-0060 `filter` block) — distinct
|
|
7
|
+
* from `lib/providers/registry.mjs`'s `.cx/providers.json`, which registers
|
|
8
|
+
* *which provider module* answers for an id, not its call-time config.
|
|
9
|
+
*
|
|
10
|
+
* Storage: one JSON file per provider id at `<rootDir>/.cx/providers/<id>.json`,
|
|
11
|
+
* `{ providerId, config, updatedAt }`. Human-reviewable, git-trackable, and
|
|
12
|
+
* namespaced per id so concurrent `configure` calls on different providers
|
|
13
|
+
* never collide on one shared file.
|
|
14
|
+
*
|
|
15
|
+
* Validation walks the provider's `configSchema` (a JSON-Schema draft
|
|
16
|
+
* 2020-12 subset: `type`, `properties`, `required`, `enum`, `pattern`,
|
|
17
|
+
* `minimum`/`maximum`, `additionalProperties`) by hand — no schema engine
|
|
18
|
+
* dependency, mirroring the hand-rolled approach `contract.mjs` already
|
|
19
|
+
* takes for the ADR-0060 filter block. `config.filter`, when present, is
|
|
20
|
+
* delegated to `validateFilterConfig()` from `contract.mjs` (LMCP-B11) so
|
|
21
|
+
* the two validators can never drift on filter-block semantics. Every
|
|
22
|
+
* rejection carries a JSON-Pointer-style path (`config.<field>` or
|
|
23
|
+
* `config.filter.<...>`) so the caller can name the offending key.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
27
|
+
import { dirname, join } from 'node:path';
|
|
28
|
+
|
|
29
|
+
import { validateFilterConfig } from './contract.mjs';
|
|
30
|
+
import { SCOPE_KEYS, PREDICATE_KEYS } from './filter-schema.mjs';
|
|
31
|
+
|
|
32
|
+
// `filter.scope.*` and most `filter.predicates.*` leaves are always arrays
|
|
33
|
+
// per the ADR-0060 grammar (filter-schema.mjs); `updatedSince` is the one
|
|
34
|
+
// scalar-string predicate. A single `--filter.scope.projects ABC` must
|
|
35
|
+
// therefore land as `["ABC"]`, not the bare string, even on first occurrence.
|
|
36
|
+
const ALWAYS_ARRAY_FILTER_LEAVES = new Set([
|
|
37
|
+
...SCOPE_KEYS.map((k) => `filter.scope.${k}`),
|
|
38
|
+
...PREDICATE_KEYS.filter((k) => k !== 'updatedSince').map((k) => `filter.predicates.${k}`),
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
export function instanceConfigPath(rootDir, providerId) {
|
|
42
|
+
return join(rootDir, '.cx', 'providers', `${providerId}.json`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function readInstanceConfig(rootDir, providerId) {
|
|
46
|
+
const filePath = instanceConfigPath(rootDir, providerId);
|
|
47
|
+
if (!existsSync(filePath)) return null;
|
|
48
|
+
return JSON.parse(readFileSync(filePath, 'utf8'));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function writeInstanceConfig(rootDir, providerId, config) {
|
|
52
|
+
const filePath = instanceConfigPath(rootDir, providerId);
|
|
53
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
54
|
+
const record = { providerId, config, updatedAt: new Date().toISOString() };
|
|
55
|
+
writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n');
|
|
56
|
+
return record;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build the default config object from a JSON-Schema `properties` map:
|
|
61
|
+
* every property that declares a `default` is seeded with it. Properties
|
|
62
|
+
* without a default are omitted — `provider add` scaffolds only what the
|
|
63
|
+
* schema actually commits to, not placeholder nulls.
|
|
64
|
+
*/
|
|
65
|
+
export function defaultsFromSchema(configSchema) {
|
|
66
|
+
const defaults = {};
|
|
67
|
+
const properties = configSchema?.properties || {};
|
|
68
|
+
for (const [key, propSchema] of Object.entries(properties)) {
|
|
69
|
+
if (propSchema && Object.prototype.hasOwnProperty.call(propSchema, 'default')) {
|
|
70
|
+
defaults[key] = propSchema.default;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return defaults;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function typeMatches(value, type) {
|
|
77
|
+
switch (type) {
|
|
78
|
+
case 'string': return typeof value === 'string';
|
|
79
|
+
case 'number': return typeof value === 'number' && Number.isFinite(value);
|
|
80
|
+
case 'integer': return typeof value === 'number' && Number.isInteger(value);
|
|
81
|
+
case 'boolean': return typeof value === 'boolean';
|
|
82
|
+
case 'array': return Array.isArray(value);
|
|
83
|
+
case 'object': return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
84
|
+
default: return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Validate one property value against its JSON-Schema property descriptor.
|
|
90
|
+
* Returns an array of error strings (empty when valid); each string is
|
|
91
|
+
* prefixed with `path` so nested callers can build a JSON-Pointer trail.
|
|
92
|
+
*/
|
|
93
|
+
function validateProperty(path, value, propSchema) {
|
|
94
|
+
const errors = [];
|
|
95
|
+
if (!propSchema) return errors;
|
|
96
|
+
|
|
97
|
+
if (propSchema.enum && !propSchema.enum.includes(value)) {
|
|
98
|
+
errors.push(`${path}: must be one of [${propSchema.enum.join(', ')}], got ${JSON.stringify(value)}`);
|
|
99
|
+
return errors;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (propSchema.type && !typeMatches(value, propSchema.type)) {
|
|
103
|
+
errors.push(`${path}: must be of type ${propSchema.type}, got ${JSON.stringify(value)}`);
|
|
104
|
+
return errors;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (propSchema.pattern && typeof value === 'string' && !new RegExp(propSchema.pattern).test(value)) {
|
|
108
|
+
errors.push(`${path}: does not match pattern ${propSchema.pattern}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (typeof value === 'number') {
|
|
112
|
+
if (propSchema.minimum !== undefined && value < propSchema.minimum) {
|
|
113
|
+
errors.push(`${path}: must be >= ${propSchema.minimum}, got ${value}`);
|
|
114
|
+
}
|
|
115
|
+
if (propSchema.maximum !== undefined && value > propSchema.maximum) {
|
|
116
|
+
errors.push(`${path}: must be <= ${propSchema.maximum}, got ${value}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return errors;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validate a merged instance config against a provider's declared
|
|
125
|
+
* `configSchema`, plus (when the config carries a `filter` key) the
|
|
126
|
+
* ADR-0060 filter block via `validateFilterConfig()`. Returns
|
|
127
|
+
* `{ valid: true }` or `{ valid: false, errors: string[] }` — never throws,
|
|
128
|
+
* so callers (CLI + tests) get a uniform result shape to report from.
|
|
129
|
+
*
|
|
130
|
+
* `filter` is accepted on every provider config regardless of whether the
|
|
131
|
+
* schema's `properties` declares it: the ADR-0060 block is a cross-cutting
|
|
132
|
+
* concern layered on top of the provider-specific schema, not a provider-
|
|
133
|
+
* specific property.
|
|
134
|
+
*/
|
|
135
|
+
export function validateInstanceConfig(providerId, configSchema, config = {}) {
|
|
136
|
+
const errors = [];
|
|
137
|
+
const schema = configSchema || {};
|
|
138
|
+
const properties = schema.properties || {};
|
|
139
|
+
const { filter, ...rest } = config || {};
|
|
140
|
+
|
|
141
|
+
for (const required of schema.required || []) {
|
|
142
|
+
if (!(required in rest)) {
|
|
143
|
+
errors.push(`config.${required}: required property missing`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
148
|
+
const path = `config.${key}`;
|
|
149
|
+
if (!(key in properties)) {
|
|
150
|
+
if (schema.additionalProperties === false) {
|
|
151
|
+
errors.push(`${path}: unknown property (not declared in configSchema)`);
|
|
152
|
+
}
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
errors.push(...validateProperty(path, value, properties[key]));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (filter !== undefined) {
|
|
159
|
+
try {
|
|
160
|
+
validateFilterConfig(providerId, filter);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
errors.push(`config.filter: ${err.message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (errors.length > 0) return { valid: false, errors };
|
|
167
|
+
return { valid: true };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Apply `--key value` CLI overrides onto a base config object. Dotted keys
|
|
172
|
+
* (`scope.projects`) address nested objects; repeated flags for an array
|
|
173
|
+
* leaf (`filter.scope.projects`) accumulate rather than overwrite, so
|
|
174
|
+
* `--filter.scope.projects ABC --filter.scope.projects DEF` yields
|
|
175
|
+
* `["ABC", "DEF"]` in one `configure` call.
|
|
176
|
+
*/
|
|
177
|
+
export function applyOverrides(base, overrides) {
|
|
178
|
+
const result = JSON.parse(JSON.stringify(base || {}));
|
|
179
|
+
|
|
180
|
+
// Accumulation only fires for a keyPath repeated within *this* override
|
|
181
|
+
// batch — a pre-existing base value (a schema default or a prior
|
|
182
|
+
// `configure` call) is a plain overwrite, never folded into an array.
|
|
183
|
+
const touched = new Set();
|
|
184
|
+
|
|
185
|
+
for (const { keyPath, value } of overrides) {
|
|
186
|
+
const segments = keyPath.split('.');
|
|
187
|
+
let cursor = result;
|
|
188
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
189
|
+
const segment = segments[i];
|
|
190
|
+
if (typeof cursor[segment] !== 'object' || cursor[segment] === null || Array.isArray(cursor[segment])) {
|
|
191
|
+
cursor[segment] = {};
|
|
192
|
+
}
|
|
193
|
+
cursor = cursor[segment];
|
|
194
|
+
}
|
|
195
|
+
const leaf = segments[segments.length - 1];
|
|
196
|
+
const parsed = parseFlagValue(value);
|
|
197
|
+
if (touched.has(keyPath)) {
|
|
198
|
+
cursor[leaf] = Array.isArray(cursor[leaf]) ? [...cursor[leaf], parsed] : [cursor[leaf], parsed];
|
|
199
|
+
} else if (ALWAYS_ARRAY_FILTER_LEAVES.has(keyPath)) {
|
|
200
|
+
cursor[leaf] = [parsed];
|
|
201
|
+
touched.add(keyPath);
|
|
202
|
+
} else {
|
|
203
|
+
cursor[leaf] = parsed;
|
|
204
|
+
touched.add(keyPath);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function parseFlagValue(raw) {
|
|
211
|
+
if (raw === 'true') return true;
|
|
212
|
+
if (raw === 'false') return false;
|
|
213
|
+
if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
|
|
214
|
+
return raw;
|
|
215
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/providers/op-locate.mjs — resolve the 1Password `op` CLI binary once.
|
|
3
|
+
*
|
|
4
|
+
* A GUI-launched MCP host (OpenCode, Claude Desktop, Cursor) spawns
|
|
5
|
+
* `node lib/mcp/server.mjs` with a minimal PATH that omits Homebrew, so a bare
|
|
6
|
+
* `spawnSync('op', …)` ENOENTs and `op` is misreported as not installed despite
|
|
7
|
+
* being present at a well-known path like /opt/homebrew/bin/op. Resolution walks three
|
|
8
|
+
* tiers — the process PATH (cheap, no shell), then a login-shell probe that re-runs
|
|
9
|
+
* the user's rc/profile (macOS path_helper included, the way credential-bootstrap
|
|
10
|
+
* recovers PATH), then well-known install dirs — and caches the absolute path for
|
|
11
|
+
* the process lifetime. Every op-invocation site (secret-resolver, op-run,
|
|
12
|
+
* credential-bootstrap) resolves through here so PATH semantics stay consistent
|
|
13
|
+
* across call sites. env/runShell/wellKnown are injectable so tests exercise each
|
|
14
|
+
* tier without a real 1Password install.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'node:fs';
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
const WELL_KNOWN_POSIX = ['/opt/homebrew/bin/op', '/usr/local/bin/op', '/usr/bin/op'];
|
|
22
|
+
|
|
23
|
+
function wellKnownWin32() {
|
|
24
|
+
const local = process.env.LOCALAPPDATA;
|
|
25
|
+
return [
|
|
26
|
+
local ? path.join(local, 'Microsoft', 'WinGet', 'Links', 'op.exe') : null,
|
|
27
|
+
'C:\\ProgramData\\chocolatey\\bin\\op.exe',
|
|
28
|
+
].filter(Boolean);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function defaultWellKnown() {
|
|
32
|
+
return process.platform === 'win32' ? wellKnownWin32() : WELL_KNOWN_POSIX;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let cached;
|
|
36
|
+
|
|
37
|
+
function isFile(candidate) {
|
|
38
|
+
try {
|
|
39
|
+
return fs.statSync(candidate).isFile();
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Scan the process PATH for `op` without spawning a shell — the common case where
|
|
46
|
+
// PATH is already correct resolves here at no cost.
|
|
47
|
+
|
|
48
|
+
function scanProcessPath(env) {
|
|
49
|
+
const dirs = String(env?.PATH || '').split(path.delimiter).filter(Boolean);
|
|
50
|
+
const names = process.platform === 'win32' ? ['op.exe', 'op.cmd', 'op.bat'] : ['op'];
|
|
51
|
+
for (const dir of dirs) {
|
|
52
|
+
for (const name of names) {
|
|
53
|
+
const candidate = path.join(dir, name);
|
|
54
|
+
if (isFile(candidate)) return candidate;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Login-shell probe: `sh -lc 'command -v op'` re-runs the user's shell init, which
|
|
61
|
+
// on macOS is where path_helper adds Homebrew — recovering the PATH a GUI-launched
|
|
62
|
+
// child never inherited. `where op` is the Windows analogue.
|
|
63
|
+
|
|
64
|
+
function defaultRunShell() {
|
|
65
|
+
if (process.platform === 'win32') {
|
|
66
|
+
const r = spawnSync('where', ['op'], { encoding: 'utf8', timeout: 3000 });
|
|
67
|
+
return r.status === 0 ? String(r.stdout || '').split(/\r?\n/)[0].trim() : '';
|
|
68
|
+
}
|
|
69
|
+
const r = spawnSync('sh', ['-lc', 'command -v op'], { encoding: 'utf8', timeout: 3000 });
|
|
70
|
+
return r.status === 0 ? String(r.stdout || '').trim() : '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Resolve the `op` binary (process PATH → login-shell PATH → well-known dirs),
|
|
75
|
+
* returning an absolute path or null. Cached for the process lifetime; pass
|
|
76
|
+
* fresh:true to bypass the cache. env/runShell/wellKnown are injectable for tests.
|
|
77
|
+
*/
|
|
78
|
+
export function locateOpBinary({ env = process.env, runShell = defaultRunShell, wellKnown = defaultWellKnown(), fresh = false } = {}) {
|
|
79
|
+
if (!fresh && cached !== undefined) return cached;
|
|
80
|
+
|
|
81
|
+
const onPath = scanProcessPath(env);
|
|
82
|
+
if (onPath) return (cached = onPath);
|
|
83
|
+
|
|
84
|
+
const viaShell = runShell();
|
|
85
|
+
if (viaShell && isFile(viaShell)) return (cached = viaShell);
|
|
86
|
+
|
|
87
|
+
for (const candidate of wellKnown) {
|
|
88
|
+
if (candidate && isFile(candidate)) return (cached = candidate);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return (cached = null);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function __resetOpLocateCache() {
|
|
95
|
+
cached = undefined;
|
|
96
|
+
}
|
package/lib/providers/op-run.mjs
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/providers/op-run.mjs — optionally launch
|
|
2
|
+
* lib/providers/op-run.mjs — optionally launch Construct under 1Password `op run`.
|
|
3
3
|
*
|
|
4
|
-
* When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines)
|
|
5
|
-
*
|
|
6
|
-
* `op run --env-file <file> --
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
4
|
+
* When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines) and
|
|
5
|
+
* the `op` CLI is installed, `construct dev` re-execs itself once under
|
|
6
|
+
* `op run --env-file <file> -- …` (maybeReExecUnderOpRun). Every op:// reference then
|
|
7
|
+
* resolves a single time at that stable parent — one biometric unlock — and the
|
|
8
|
+
* detached daemons the re-exec'd process spawns inherit the resolved keys through the
|
|
9
|
+
* parent env. A CONSTRUCT_OP_RUN_ACTIVE sentinel marks the wrapped process so nested
|
|
10
|
+
* launches inside it do not re-wrap and re-prompt.
|
|
11
|
+
*
|
|
12
|
+
* wrapWithOpRun stays the per-service fallback for daemons started outside the
|
|
13
|
+
* re-exec'd parent (a doctor or oracle restart from a hook), where no parent
|
|
14
|
+
* resolution has run; under the parent (sentinel set) it returns the command
|
|
15
|
+
* unchanged. The trade-off of resolving once at the parent: `op run` masks the
|
|
16
|
+
* parent's own stdout but not the separate log files of the daemons it detaches, so a
|
|
17
|
+
* daemon that echoes a key logs it unmasked (ADR-0049). The per-service fallback still
|
|
18
|
+
* masks the daemons it wraps directly.
|
|
11
19
|
*
|
|
12
20
|
* Strictly opt-in: with the var unset, the file missing, or `op` absent, the
|
|
13
21
|
* command is returned unchanged and 1Password is never invoked. No other setup
|
|
@@ -17,6 +25,13 @@
|
|
|
17
25
|
import fs from 'node:fs';
|
|
18
26
|
import os from 'node:os';
|
|
19
27
|
import { spawnSync } from 'node:child_process';
|
|
28
|
+
import { locateOpBinary } from './op-locate.mjs';
|
|
29
|
+
|
|
30
|
+
// Set on the process env when Construct has already re-exec'd under a parent `op run`.
|
|
31
|
+
// Both the parent re-exec and the per-service wrap check it so a single resolution at
|
|
32
|
+
// the top of the tree is never nested (which would re-prompt and double-resolve).
|
|
33
|
+
|
|
34
|
+
export const OP_RUN_ACTIVE_ENV = 'CONSTRUCT_OP_RUN_ACTIVE';
|
|
20
35
|
|
|
21
36
|
function expandHome(file, homeDir) {
|
|
22
37
|
if (file === '~') return homeDir;
|
|
@@ -36,8 +51,10 @@ export function resolveOpEnvFile(env = process.env, homeDir = os.homedir()) {
|
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
export function opCliAvailable() {
|
|
54
|
+
const opPath = locateOpBinary();
|
|
55
|
+
if (!opPath) return false;
|
|
39
56
|
try {
|
|
40
|
-
return spawnSync(
|
|
57
|
+
return spawnSync(opPath, ['--version'], { stdio: 'ignore', timeout: 3000 }).status === 0;
|
|
41
58
|
} catch {
|
|
42
59
|
return false;
|
|
43
60
|
}
|
|
@@ -48,6 +65,10 @@ export function opCliAvailable() {
|
|
|
48
65
|
* to the original command unchanged otherwise. `hasOp` is injectable for tests.
|
|
49
66
|
*/
|
|
50
67
|
export function wrapWithOpRun(command, args = [], { env = process.env, homeDir = os.homedir(), hasOp = opCliAvailable } = {}) {
|
|
68
|
+
// A parent `op run` already resolved every reference into this env; wrapping again
|
|
69
|
+
// would spawn a nested op run that re-prompts, so hand back the command untouched.
|
|
70
|
+
if (env[OP_RUN_ACTIVE_ENV]) return { command, args, wrapped: false, envFile: null };
|
|
71
|
+
|
|
51
72
|
const file = resolveOpEnvFile(env, homeDir);
|
|
52
73
|
if (!file || !hasOp()) return { command, args, wrapped: false, envFile: null };
|
|
53
74
|
return {
|
|
@@ -57,3 +78,37 @@ export function wrapWithOpRun(command, args = [], { env = process.env, homeDir =
|
|
|
57
78
|
envFile: file,
|
|
58
79
|
};
|
|
59
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Re-exec the current process once under `op run` so every op:// reference in the
|
|
84
|
+
* env-file resolves a single time into the process env; the detached daemons the
|
|
85
|
+
* re-exec'd process then spawns inherit the resolved keys without their own op run.
|
|
86
|
+
* The blocking spawnSync makes the outer process a thin wrapper that exits with the
|
|
87
|
+
* inner status. Returns { reExecuted: false, reason } on every opt-out path — sentinel
|
|
88
|
+
* already set, no resolvable env-file, `op` absent, or a spawn error — so the caller
|
|
89
|
+
* falls through to the normal (per-service-wrap) launch unchanged. `spawnFn` and the
|
|
90
|
+
* process descriptors are injectable for tests.
|
|
91
|
+
*/
|
|
92
|
+
export function maybeReExecUnderOpRun({
|
|
93
|
+
argv = process.argv,
|
|
94
|
+
execPath = process.execPath,
|
|
95
|
+
env = process.env,
|
|
96
|
+
homeDir = os.homedir(),
|
|
97
|
+
hasOp = opCliAvailable,
|
|
98
|
+
spawnFn = spawnSync,
|
|
99
|
+
} = {}) {
|
|
100
|
+
if (env[OP_RUN_ACTIVE_ENV]) return { reExecuted: false, reason: 'already-active' };
|
|
101
|
+
const file = resolveOpEnvFile(env, homeDir);
|
|
102
|
+
if (!file) return { reExecuted: false, reason: 'not-opted-in' };
|
|
103
|
+
if (!hasOp()) return { reExecuted: false, reason: 'op-missing' };
|
|
104
|
+
|
|
105
|
+
const scriptArgs = argv.slice(1);
|
|
106
|
+
const childEnv = { ...env, [OP_RUN_ACTIVE_ENV]: '1' };
|
|
107
|
+
const result = spawnFn('op', ['run', `--env-file=${file}`, '--', execPath, ...scriptArgs], {
|
|
108
|
+
stdio: 'inherit',
|
|
109
|
+
env: childEnv,
|
|
110
|
+
});
|
|
111
|
+
if (result?.error) return { reExecuted: false, reason: 'spawn-failed', error: result.error };
|
|
112
|
+
const code = typeof result?.status === 'number' ? result.status : 1;
|
|
113
|
+
return { reExecuted: true, code, envFile: file };
|
|
114
|
+
}
|
|
@@ -26,10 +26,20 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
26
26
|
import { assertProviderContract } from './contract.mjs';
|
|
27
27
|
import { getBreaker } from './circuit-breaker.mjs';
|
|
28
28
|
import { configDir } from '../config/xdg.mjs';
|
|
29
|
+
import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
|
|
30
|
+
import { describeModelProviders } from '../models/catalog.mjs';
|
|
29
31
|
|
|
30
32
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @deprecated Use manifest-driven provider discovery via resolveProviders() instead.
|
|
36
|
+
* Will be removed in a future version.
|
|
37
|
+
*/
|
|
38
|
+
export const BUILT_INS = (() => {
|
|
39
|
+
const { builtin: dir } = resolveManifestDirs();
|
|
40
|
+
const { manifests } = loadManifestsFromDir(dir);
|
|
41
|
+
return manifests.filter((m) => m.kind === 'data-source').map((m) => m.id);
|
|
42
|
+
})();
|
|
33
43
|
|
|
34
44
|
function readJsonIfExists(path) {
|
|
35
45
|
try {
|
|
@@ -109,7 +119,13 @@ export async function resolveProviders({ rootDir = process.cwd(), env = process.
|
|
|
109
119
|
const sources = {};
|
|
110
120
|
const errors = [];
|
|
111
121
|
|
|
112
|
-
|
|
122
|
+
const { builtin: builtinManifestDir } = resolveManifestDirs();
|
|
123
|
+
const { manifests: allManifests } = loadManifestsFromDir(builtinManifestDir);
|
|
124
|
+
const builtInIds = allManifests
|
|
125
|
+
.filter((m) => m.kind === 'data-source')
|
|
126
|
+
.map((m) => m.id);
|
|
127
|
+
|
|
128
|
+
for (const id of builtInIds) {
|
|
113
129
|
try {
|
|
114
130
|
const mod = await loadBuiltIn(id);
|
|
115
131
|
if (!mod) continue;
|
|
@@ -145,6 +161,12 @@ export async function resolveProviders({ rootDir = process.cwd(), env = process.
|
|
|
145
161
|
return { providers, sources, errors };
|
|
146
162
|
}
|
|
147
163
|
|
|
164
|
+
// kind='model' manifests (LMCP-B4) enumerate through this same registry but
|
|
165
|
+
// are never instantiated as data-source contract instances — model provider
|
|
166
|
+
// families keep their cache/poll/visibility logic in lib/models/catalog.mjs
|
|
167
|
+
// and lib/model-router.mjs. This appends their manifest-derived summary rows
|
|
168
|
+
// so `construct provider list` shows one merged surface across both kinds.
|
|
169
|
+
|
|
148
170
|
export async function describeProviders({ rootDir = process.cwd(), env = process.env } = {}) {
|
|
149
171
|
const { providers, sources, errors } = await resolveProviders({ rootDir, env });
|
|
150
172
|
const summary = await Promise.all(
|
|
@@ -161,7 +183,8 @@ export async function describeProviders({ rootDir = process.cwd(), env = process
|
|
|
161
183
|
};
|
|
162
184
|
})
|
|
163
185
|
);
|
|
164
|
-
|
|
186
|
+
const modelSummary = describeModelProviders({ rootDir, env });
|
|
187
|
+
return { summary: [...summary, ...modelSummary], errors };
|
|
165
188
|
}
|
|
166
189
|
|
|
167
190
|
// Standalone validation for a candidate override entry: imports the package
|