@geraldmaron/construct 1.4.2 → 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 +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 +55 -38
- 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 +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 +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 +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 +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
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/manifest-schema.mjs — canonical pack manifest schema descriptor.
|
|
3
|
+
*
|
|
4
|
+
* Defines the vocabulary for pack manifests as per ADR-0055 (A5 pack schema).
|
|
5
|
+
* A plain JS descriptor, not JSON Schema — the validator in validate.mjs
|
|
6
|
+
* consumes it directly. The schema is intentionally additive: new optional
|
|
7
|
+
* fields may be added in a minor version bump; new required fields are
|
|
8
|
+
* breaking and require a PACK_COMPAT_VERSION increment.
|
|
9
|
+
*
|
|
10
|
+
* `embedBindings` (LMCP-E4) is one such additive field: per-specialist
|
|
11
|
+
* provider read/search grants and external-write proposal grants, closing
|
|
12
|
+
* the excessive-agency gap where an embedded specialist could read any
|
|
13
|
+
* provider and propose any write with no declared authorization.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const PACK_REQUIRED_FIELDS = ['id', 'version', 'compatVersion'];
|
|
17
|
+
|
|
18
|
+
export const PACK_OPTIONAL_FIELDS = [
|
|
19
|
+
'teams', 'specialists', 'prompts', 'frameworks', 'perspectives',
|
|
20
|
+
'modelTierHints', 'toolGrantsRequested', 'workflowContributions',
|
|
21
|
+
'handoffContracts', 'outputContracts', 'gates', 'tests', 'docs',
|
|
22
|
+
'installConditions', 'enableConditions', 'deprecation', 'embedBindings',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const PACK_COMPAT_VERSION = 1;
|
|
26
|
+
export const PACK_ID_RE = /^[a-z0-9\-./@]+$/;
|
|
27
|
+
export const PACK_SOURCE_TIERS = ['builtin', 'user', 'project'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* embedBindings shape (LMCP-E4) — per-specialist read/propose grant.
|
|
31
|
+
*
|
|
32
|
+
* `embedBindings` is a pack-level map keyed by specialist id:
|
|
33
|
+
*
|
|
34
|
+
* {
|
|
35
|
+
* "<specialistId>": {
|
|
36
|
+
* "providers": [
|
|
37
|
+
* { "id": "<extension-manifest-id>", "capabilities": ["read"], "filters": { ... ADR-0060 filter block ... } }
|
|
38
|
+
* ],
|
|
39
|
+
* "proposals": ["jira.createIssue"]
|
|
40
|
+
* }
|
|
41
|
+
* }
|
|
42
|
+
*
|
|
43
|
+
* `providers[].capabilities` values must be drawn from EMBED_BINDING_CAPABILITIES
|
|
44
|
+
* and must each be declared by the referenced extension manifest's own
|
|
45
|
+
* `capabilities` array — the binding cannot grant a capability the provider
|
|
46
|
+
* itself never advertises. `providers[].filters` reuses the ADR-0060 provider
|
|
47
|
+
* filter block verbatim (scope/predicates/nativeQuery); it is optional and,
|
|
48
|
+
* when present, validated with the same rules poll-time filters use.
|
|
49
|
+
* `proposals` is a flat list of `<providerId>.<writeKind>` tokens the
|
|
50
|
+
* specialist may propose (never execute autonomously — see authority-guard.mjs).
|
|
51
|
+
*/
|
|
52
|
+
export const EMBED_BINDING_CAPABILITIES = ['read', 'search'];
|
|
53
|
+
|
|
54
|
+
export const EMBED_BINDING_FIELDS = ['providers', 'proposals'];
|
|
55
|
+
|
|
56
|
+
export const EMBED_BINDING_PROVIDER_FIELDS = ['id', 'capabilities', 'filters'];
|
|
57
|
+
|
|
58
|
+
export const EMBED_BINDING_PROPOSAL_RE = /^[a-z0-9-]+\.[a-zA-Z0-9]+$/;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/prompts.mjs — pack prompt-file validation and registry resolution.
|
|
3
|
+
*
|
|
4
|
+
* A pack manifest's `prompts` map declares `{ specialistId: relativePath }`,
|
|
5
|
+
* relative to that pack's own directory (`_packDir`, set by
|
|
6
|
+
* lib/packs/loader.mjs for every manifest-loaded pack) — a project pack's
|
|
7
|
+
* prompt paths are relative to the project, not to the Construct install.
|
|
8
|
+
* The core pack (lib/packs/core-pack.mjs) has no `_packDir`; its prompts map
|
|
9
|
+
* uses paths relative to `packageRoot` (the Construct install), the fallback
|
|
10
|
+
* root used whenever a pack carries no `_packDir` of its own.
|
|
11
|
+
*
|
|
12
|
+
* validatePackPrompts() confirms every declared file exists AND carries
|
|
13
|
+
* parseable YAML frontmatter with a `role` field — the two things worker.mjs
|
|
14
|
+
* needs before it can trust a persona prompt. In team/enterprise deployment
|
|
15
|
+
* mode a miss is a hard load-time error (the pack is rejected, naming the
|
|
16
|
+
* missing file) per LMCP-E2: a governed pack must never let a specialist run
|
|
17
|
+
* under the wrong persona.
|
|
18
|
+
*
|
|
19
|
+
* resolvePersonaPrompt() is the ONLY path worker.mjs uses to read a persona
|
|
20
|
+
* body: a walk of the pack list in the order given (the caller sorts by tier
|
|
21
|
+
* precedence — project before user before builtin, per ADR-0055), returning
|
|
22
|
+
* the first pack that declares the role, or `{ found: false }` when no pack
|
|
23
|
+
* in the registry declares it. No fallback to reading specialists/prompts/
|
|
24
|
+
* directly exists — that would defeat the pack boundary the registry is
|
|
25
|
+
* meant to enforce.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
29
|
+
import { join } from 'node:path';
|
|
30
|
+
import yaml from 'js-yaml';
|
|
31
|
+
|
|
32
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
33
|
+
|
|
34
|
+
function parseFrontmatter(content) {
|
|
35
|
+
const match = content.match(FRONTMATTER_RE);
|
|
36
|
+
if (!match) return null;
|
|
37
|
+
try {
|
|
38
|
+
return yaml.load(match[1]) || {};
|
|
39
|
+
} catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function packPromptRoot(pack, fallbackRoot) {
|
|
45
|
+
return pack?._packDir || fallbackRoot;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Validate every prompt file a pack declares.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} pack a manifest object carrying `prompts: {id: relPath}`
|
|
52
|
+
* @param {object} opts
|
|
53
|
+
* @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
|
|
54
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
55
|
+
*/
|
|
56
|
+
export function validatePackPrompts(pack, { packageRoot } = {}) {
|
|
57
|
+
const errors = [];
|
|
58
|
+
const prompts = pack?.prompts || {};
|
|
59
|
+
const root = packPromptRoot(pack, packageRoot);
|
|
60
|
+
|
|
61
|
+
for (const [specId, relPath] of Object.entries(prompts)) {
|
|
62
|
+
if (typeof relPath !== 'string' || !relPath) {
|
|
63
|
+
errors.push(`pack '${pack.id}': prompt entry for '${specId}' has no file path`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
const absPath = join(root, relPath);
|
|
67
|
+
if (!existsSync(absPath)) {
|
|
68
|
+
errors.push(`pack '${pack.id}': declared prompt file missing for '${specId}': ${relPath}`);
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = readFileSync(absPath, 'utf8');
|
|
74
|
+
} catch (err) {
|
|
75
|
+
errors.push(`pack '${pack.id}': declared prompt file unreadable for '${specId}': ${relPath} (${err.message})`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const fm = parseFrontmatter(raw);
|
|
79
|
+
if (!fm) {
|
|
80
|
+
errors.push(`pack '${pack.id}': prompt file missing or invalid frontmatter for '${specId}': ${relPath}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (!fm.role || typeof fm.role !== 'string') {
|
|
84
|
+
errors.push(`pack '${pack.id}': prompt frontmatter missing 'role' for '${specId}': ${relPath}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { valid: errors.length === 0, errors };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Resolve one specialist's persona prompt through the pack registry only.
|
|
93
|
+
*
|
|
94
|
+
* Packs are tried in the order given (the caller sorts by tier precedence;
|
|
95
|
+
* see the module header) — the first pack declaring the role wins.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} role specialist id, e.g. 'cx-engineer' (accepts 'engineer' too)
|
|
98
|
+
* @param {object} opts
|
|
99
|
+
* @param {object[]} opts.packs pack list, tier-precedence ordered (see loadAllPacks)
|
|
100
|
+
* @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
|
|
101
|
+
* @returns {{found: true, content: string, packId: string} | {found: false}}
|
|
102
|
+
*/
|
|
103
|
+
export function resolvePersonaPrompt(role, { packs, packageRoot }) {
|
|
104
|
+
const slug = String(role || '').replace(/^cx-/, '');
|
|
105
|
+
const specId = `cx-${slug}`;
|
|
106
|
+
|
|
107
|
+
for (const pack of packs || []) {
|
|
108
|
+
const relPath = pack?.prompts?.[specId];
|
|
109
|
+
if (!relPath) continue;
|
|
110
|
+
const absPath = join(packPromptRoot(pack, packageRoot), relPath);
|
|
111
|
+
if (!existsSync(absPath)) continue;
|
|
112
|
+
try {
|
|
113
|
+
return { found: true, content: readFileSync(absPath, 'utf8'), packId: pack.id };
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { found: false };
|
|
120
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/validate.mjs — pack manifest validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates a plain-JS pack manifest object against the canonical schema
|
|
5
|
+
* defined in manifest-schema.mjs. All errors are returned in a structured
|
|
6
|
+
* result; this function never throws. Error messages are single-line,
|
|
7
|
+
* actionable, and prefixed with the filePath (when provided).
|
|
8
|
+
*
|
|
9
|
+
* `embedBindings` validation (LMCP-E4) additionally cross-checks each bound
|
|
10
|
+
* provider id and capability against `knownProviders` — a map of provider id
|
|
11
|
+
* to its loaded extension manifest (lib/extensions/loader.mjs). A binding
|
|
12
|
+
* naming a provider id absent from that map, or granting a capability the
|
|
13
|
+
* provider's own manifest does not declare, fails validation with a path.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {
|
|
17
|
+
PACK_REQUIRED_FIELDS, PACK_OPTIONAL_FIELDS, PACK_COMPAT_VERSION,
|
|
18
|
+
EMBED_BINDING_CAPABILITIES, EMBED_BINDING_FIELDS, EMBED_BINDING_PROVIDER_FIELDS,
|
|
19
|
+
EMBED_BINDING_PROPOSAL_RE,
|
|
20
|
+
} from './manifest-schema.mjs';
|
|
21
|
+
import { validateFilterConfig } from '../providers/contract.mjs';
|
|
22
|
+
|
|
23
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(?:[-+].+)?$/;
|
|
24
|
+
|
|
25
|
+
const PACK_ID_RE = /^[a-z0-9\-./@]+$/;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Validate the `embedBindings` block (LMCP-E4) against known extension
|
|
29
|
+
* manifests. `knownProviders` is a map of providerId → manifest (as loaded
|
|
30
|
+
* by lib/extensions/loader.mjs); each bound provider must exist in that map
|
|
31
|
+
* and must itself declare every capability the binding grants — a pack
|
|
32
|
+
* cannot hand a specialist a read/search grant the provider never advertises.
|
|
33
|
+
* Every error names the JSON path of the offending field.
|
|
34
|
+
*/
|
|
35
|
+
function validateEmbedBindings(embedBindings, { prefix, knownProviders = {} }) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
|
|
38
|
+
if (typeof embedBindings !== 'object' || embedBindings === null || Array.isArray(embedBindings)) {
|
|
39
|
+
return [`${prefix}embedBindings must be an object keyed by specialist id`];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const [specialistId, binding] of Object.entries(embedBindings)) {
|
|
43
|
+
const bindingPath = `embedBindings.${specialistId}`;
|
|
44
|
+
|
|
45
|
+
if (typeof binding !== 'object' || binding === null || Array.isArray(binding)) {
|
|
46
|
+
errors.push(`${prefix}${bindingPath} must be an object`);
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const key of Object.keys(binding)) {
|
|
51
|
+
if (!EMBED_BINDING_FIELDS.includes(key)) {
|
|
52
|
+
errors.push(`${prefix}${bindingPath}.${key} is not a recognized embedBindings field (allowed: ${EMBED_BINDING_FIELDS.join(', ')})`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const providers = binding.providers;
|
|
57
|
+
if (providers !== undefined) {
|
|
58
|
+
if (!Array.isArray(providers)) {
|
|
59
|
+
errors.push(`${prefix}${bindingPath}.providers must be an array`);
|
|
60
|
+
} else {
|
|
61
|
+
providers.forEach((entry, idx) => {
|
|
62
|
+
const entryPath = `${bindingPath}.providers[${idx}]`;
|
|
63
|
+
|
|
64
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
65
|
+
errors.push(`${prefix}${entryPath} must be an object`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const key of Object.keys(entry)) {
|
|
70
|
+
if (!EMBED_BINDING_PROVIDER_FIELDS.includes(key)) {
|
|
71
|
+
errors.push(`${prefix}${entryPath}.${key} is not a recognized provider binding field (allowed: ${EMBED_BINDING_PROVIDER_FIELDS.join(', ')})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const providerId = entry.id;
|
|
76
|
+
if (typeof providerId !== 'string' || providerId.length === 0) {
|
|
77
|
+
errors.push(`${prefix}${entryPath}.id must be a non-empty string`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const providerManifest = knownProviders[providerId];
|
|
82
|
+
if (!providerManifest) {
|
|
83
|
+
errors.push(`${prefix}${entryPath}.id references unknown provider "${providerId}" (no extension manifest declares this id)`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const declaredCapabilities = new Set(providerManifest.capabilities || []);
|
|
88
|
+
|
|
89
|
+
if (!Array.isArray(entry.capabilities) || entry.capabilities.length === 0) {
|
|
90
|
+
errors.push(`${prefix}${entryPath}.capabilities must be a non-empty array`);
|
|
91
|
+
} else {
|
|
92
|
+
entry.capabilities.forEach((cap, capIdx) => {
|
|
93
|
+
const capPath = `${entryPath}.capabilities[${capIdx}]`;
|
|
94
|
+
if (!EMBED_BINDING_CAPABILITIES.includes(cap)) {
|
|
95
|
+
errors.push(`${prefix}${capPath} "${cap}" is not a recognized embed capability (allowed: ${EMBED_BINDING_CAPABILITIES.join(', ')})`);
|
|
96
|
+
} else if (!declaredCapabilities.has(cap)) {
|
|
97
|
+
errors.push(`${prefix}${capPath} "${cap}" is not declared by provider "${providerId}"'s manifest capabilities (${[...declaredCapabilities].join(', ') || '(none)'})`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (entry.filters !== undefined) {
|
|
103
|
+
try {
|
|
104
|
+
validateFilterConfig(providerId, entry.filters);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
errors.push(`${prefix}${entryPath}.filters: ${err.message}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const proposals = binding.proposals;
|
|
114
|
+
if (proposals !== undefined) {
|
|
115
|
+
if (!Array.isArray(proposals)) {
|
|
116
|
+
errors.push(`${prefix}${bindingPath}.proposals must be an array`);
|
|
117
|
+
} else {
|
|
118
|
+
const boundProviderIds = new Set((Array.isArray(providers) ? providers : []).map((p) => p?.id).filter(Boolean));
|
|
119
|
+
proposals.forEach((token, idx) => {
|
|
120
|
+
const tokenPath = `${bindingPath}.proposals[${idx}]`;
|
|
121
|
+
if (typeof token !== 'string' || !EMBED_BINDING_PROPOSAL_RE.test(token)) {
|
|
122
|
+
errors.push(`${prefix}${tokenPath} "${token}" must be a "<providerId>.<writeKind>" token (e.g. "jira.createIssue")`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const [providerId] = token.split('.');
|
|
126
|
+
if (!knownProviders[providerId]) {
|
|
127
|
+
errors.push(`${prefix}${tokenPath} "${token}" references unknown provider "${providerId}" (no extension manifest declares this id)`);
|
|
128
|
+
} else if (boundProviderIds.size > 0 && !boundProviderIds.has(providerId)) {
|
|
129
|
+
errors.push(`${prefix}${tokenPath} "${token}" proposes against provider "${providerId}" which is not in this specialist's providers[] grant`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return errors;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function validatePackManifest(manifest, { filePath, strict = false, knownProviders = {} } = {}) {
|
|
140
|
+
const prefix = filePath ? `${filePath}: ` : '';
|
|
141
|
+
const errors = [];
|
|
142
|
+
|
|
143
|
+
if (!manifest || typeof manifest !== 'object' || Array.isArray(manifest)) {
|
|
144
|
+
return { valid: false, errors: [`${prefix}manifest must be a JSON object`] };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
for (const field of PACK_REQUIRED_FIELDS) {
|
|
148
|
+
if (!(field in manifest) || manifest[field] === undefined || manifest[field] === null) {
|
|
149
|
+
errors.push(`${prefix}missing required field: ${field}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if ('id' in manifest && manifest.id !== undefined && manifest.id !== null) {
|
|
154
|
+
if (typeof manifest.id !== 'string' || manifest.id.length === 0) {
|
|
155
|
+
errors.push(`${prefix}id must be a non-empty string`);
|
|
156
|
+
} else if (!PACK_ID_RE.test(manifest.id)) {
|
|
157
|
+
errors.push(`${prefix}id must match [a-z0-9-./@]+ (got '${manifest.id}')`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if ('version' in manifest && manifest.version !== undefined && manifest.version !== null) {
|
|
162
|
+
if (typeof manifest.version !== 'string' || !SEMVER_RE.test(manifest.version)) {
|
|
163
|
+
errors.push(`${prefix}version must be a semver string`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (manifest.compatVersion !== undefined && manifest.compatVersion !== null) {
|
|
168
|
+
if (typeof manifest.compatVersion !== 'number' || !Number.isInteger(manifest.compatVersion)) {
|
|
169
|
+
errors.push(`${prefix}compatVersion must be an integer`);
|
|
170
|
+
} else if (manifest.compatVersion > PACK_COMPAT_VERSION) {
|
|
171
|
+
errors.push(
|
|
172
|
+
`${prefix}compatVersion ${manifest.compatVersion} exceeds supported version ${PACK_COMPAT_VERSION}; upgrade Construct to use this pack`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (manifest.deprecation !== undefined && manifest.deprecation !== null) {
|
|
178
|
+
if (typeof manifest.deprecation !== 'object' || Array.isArray(manifest.deprecation)) {
|
|
179
|
+
errors.push(`${prefix}deprecation must be an object`);
|
|
180
|
+
} else {
|
|
181
|
+
if (!('since' in manifest.deprecation)) {
|
|
182
|
+
errors.push(`${prefix}deprecation must include 'since' field`);
|
|
183
|
+
}
|
|
184
|
+
if (!('message' in manifest.deprecation)) {
|
|
185
|
+
errors.push(`${prefix}deprecation must include 'message' field`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (manifest.embedBindings !== undefined && manifest.embedBindings !== null) {
|
|
191
|
+
errors.push(...validateEmbedBindings(manifest.embedBindings, { prefix, knownProviders }));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (strict) {
|
|
195
|
+
const knownFields = new Set([...PACK_REQUIRED_FIELDS, ...PACK_OPTIONAL_FIELDS]);
|
|
196
|
+
for (const key of Object.keys(manifest)) {
|
|
197
|
+
if (key.startsWith('_')) continue;
|
|
198
|
+
if (!knownFields.has(key)) {
|
|
199
|
+
errors.push(`${prefix}unknown field '${key}' in strict mode`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (errors.length > 0) {
|
|
205
|
+
return { valid: false, errors };
|
|
206
|
+
}
|
|
207
|
+
return { valid: true };
|
|
208
|
+
}
|
package/lib/plugin-registry.mjs
CHANGED
|
@@ -6,11 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { homedir } from "node:os";
|
|
9
|
-
import {
|
|
10
|
-
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { extname, join, resolve } from "node:path";
|
|
11
10
|
import { configDir } from "./config/xdg.mjs";
|
|
11
|
+
import { packageRoot } from "./roots.mjs";
|
|
12
12
|
|
|
13
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
13
|
export const MANIFEST_VERSION = 1;
|
|
15
14
|
|
|
16
15
|
function readJson(filePath) {
|
|
@@ -97,7 +96,7 @@ export function validatePluginManifest(manifest, { manifestPath = "<memory>" } =
|
|
|
97
96
|
|
|
98
97
|
function loadBuiltinPlugin(rootDir) {
|
|
99
98
|
const rootCatalogPath = join(rootDir, "lib", "mcp-catalog.json");
|
|
100
|
-
const catalogPath = existsSync(rootCatalogPath) ? rootCatalogPath : join(
|
|
99
|
+
const catalogPath = existsSync(rootCatalogPath) ? rootCatalogPath : join(packageRoot, "lib", "mcp-catalog.json");
|
|
101
100
|
const catalog = readJson(catalogPath);
|
|
102
101
|
const plugin = {
|
|
103
102
|
id: "construct-builtins",
|
|
@@ -142,7 +141,7 @@ function findManifestFiles(pluginDirs) {
|
|
|
142
141
|
return files.sort();
|
|
143
142
|
}
|
|
144
143
|
|
|
145
|
-
export function loadPluginRegistry({ cwd = process.cwd(), homeDir = homedir(), rootDir =
|
|
144
|
+
export function loadPluginRegistry({ cwd = process.cwd(), homeDir = homedir(), rootDir = packageRoot, env = process.env } = {}) {
|
|
146
145
|
const errors = [];
|
|
147
146
|
const plugins = [loadBuiltinPlugin(rootDir)];
|
|
148
147
|
const pluginDirs = resolvePluginDirs({ cwd, homeDir, env });
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/policy/audit-gate.mjs — mandatory-audit enforcement for enterprise mode (LMCP-H5).
|
|
3
|
+
*
|
|
4
|
+
* ADR-0057 (A7): enterprise mode promises every mutating action is audited.
|
|
5
|
+
* A broker/policy/write path that silently swallows an audit-sink failure
|
|
6
|
+
* (as lib/mcp/broker.mjs's best-effort catch does today) makes that promise
|
|
7
|
+
* false under exactly the condition it exists to cover. enforceMandatoryAudit
|
|
8
|
+
* is a policy-decision precondition: in enterprise mode, when the audit sink
|
|
9
|
+
* (lib/audit-trail.mjs#checkAuditSinkAvailable) is down, every action is
|
|
10
|
+
* denied before any role/manifest logic runs — fail closed, no
|
|
11
|
+
* CONSTRUCT_SKIP_* escape hatch. Solo and team modes never call the sink
|
|
12
|
+
* check and are unaffected.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { checkAuditSinkAvailable } from '../audit-trail.mjs';
|
|
16
|
+
|
|
17
|
+
export const AUDIT_GATE_SOURCE = 'mandatory-audit-gate';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Enterprise-only precondition: deny everything if the audit sink is down.
|
|
21
|
+
* Returns null (no opinion) for solo/team or when the sink is healthy, so
|
|
22
|
+
* callers can compose this before their own decision logic without changing
|
|
23
|
+
* behavior outside enterprise mode.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} input
|
|
26
|
+
* @param {'solo'|'team'|'enterprise'} input.deploymentMode
|
|
27
|
+
* @param {function} [input.checkSink] - injectable for tests; defaults to the real fs probe
|
|
28
|
+
* @returns {{allowed: boolean, reason: string, approvalRequired: boolean, source: string}|null}
|
|
29
|
+
*/
|
|
30
|
+
export function enforceMandatoryAudit({ deploymentMode, checkSink = checkAuditSinkAvailable } = {}) {
|
|
31
|
+
if (deploymentMode !== 'enterprise') return null;
|
|
32
|
+
|
|
33
|
+
const sink = checkSink();
|
|
34
|
+
if (sink.available) return null;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
allowed: false,
|
|
38
|
+
reason: `enterprise mode requires a healthy audit sink; audit sink unavailable (${sink.reason || 'unknown-error'}) — action refused fail-closed`,
|
|
39
|
+
approvalRequired: false,
|
|
40
|
+
source: AUDIT_GATE_SOURCE,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/policy/consumption-budget.mjs — durable per-actor/run consumption budgets (LMCP-N5).
|
|
3
|
+
*
|
|
4
|
+
* Nothing durably bounded token/tool-call/external-write/queue-submission
|
|
5
|
+
* consumption per actor or run before this: lib/mcp/broker.mjs's BrokerStore
|
|
6
|
+
* (LMCP-I1) covers only a fixed-window tool-call rate per actor+tool, reset
|
|
7
|
+
* on every window rather than accumulating for the life of a run. This store
|
|
8
|
+
* persists cumulative consumption per actor+run to
|
|
9
|
+
* `<rootDir>/.cx/consumption-budgets.json`, survives process restarts the
|
|
10
|
+
* same way BrokerStore does, and is consulted pre-execution so an exhausted
|
|
11
|
+
* run halts with a clear, durable budget-denied record instead of a
|
|
12
|
+
* surprise bill.
|
|
13
|
+
*
|
|
14
|
+
* Budgets default per deployment mode (lib/deployment-mode.mjs): solo is
|
|
15
|
+
* `null` (unbounded) — a single local operator should never hit a wall a
|
|
16
|
+
* team/enterprise multi-actor deployment needs; team/enterprise apply real
|
|
17
|
+
* per-kind caps. An explicit `budget` object always overrides the mode
|
|
18
|
+
* default.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
25
|
+
|
|
26
|
+
const BUDGET_STORE_SUBPATH = path.join('.cx', 'consumption-budgets.json');
|
|
27
|
+
|
|
28
|
+
export const CONSUMPTION_KINDS = ['tokens', 'toolCalls', 'externalWrites', 'queueSubmissions'];
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_BUDGETS_BY_MODE = Object.freeze({
|
|
31
|
+
solo: null,
|
|
32
|
+
team: Object.freeze({ tokens: 2_000_000, toolCalls: 2000, externalWrites: 200, queueSubmissions: 500 }),
|
|
33
|
+
enterprise: Object.freeze({ tokens: 5_000_000, toolCalls: 5000, externalWrites: 500, queueSubmissions: 1000 }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export function resolveDefaultBudget(mode) {
|
|
37
|
+
// `??` treats solo's deliberate `null` (unbounded) as absent and would fall
|
|
38
|
+
// through to the team default, defeating the solo-is-unbounded default —
|
|
39
|
+
// an explicit `in` check is required so null is a real, honored value.
|
|
40
|
+
return mode in DEFAULT_BUDGETS_BY_MODE ? DEFAULT_BUDGETS_BY_MODE[mode] : DEFAULT_BUDGETS_BY_MODE.team;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function budgetStorePath(rootDir) {
|
|
44
|
+
return path.join(rootDir, BUDGET_STORE_SUBPATH);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function emptyConsumption() {
|
|
48
|
+
return { tokens: 0, toolCalls: 0, externalWrites: 0, queueSubmissions: 0 };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readStoreFile(file) {
|
|
52
|
+
try {
|
|
53
|
+
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
54
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
55
|
+
} catch {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class BudgetExceeded extends Error {
|
|
61
|
+
constructor({ actor, runId, kind, cap, projected }) {
|
|
62
|
+
super(`consumption budget exceeded: actor '${actor}' run '${runId}' would reach ${projected} ${kind} against a budget of ${cap}`);
|
|
63
|
+
this.name = 'BudgetExceeded';
|
|
64
|
+
this.actor = actor;
|
|
65
|
+
this.runId = runId;
|
|
66
|
+
this.kind = kind;
|
|
67
|
+
this.cap = cap;
|
|
68
|
+
this.projected = projected;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* ConsumptionBudgetStore — file-backed durable per-actor/run consumption.
|
|
74
|
+
*
|
|
75
|
+
* Read/write mirrors lib/mcp/broker.mjs's BrokerStore: in-memory `_data` is
|
|
76
|
+
* the write buffer, `save()` persists the whole map, and every disk
|
|
77
|
+
* operation is best-effort so a disk failure never breaks the caller.
|
|
78
|
+
*/
|
|
79
|
+
export class ConsumptionBudgetStore {
|
|
80
|
+
constructor({ rootDir, file, env = process.env } = {}) {
|
|
81
|
+
if (!rootDir && !file) throw new Error('ConsumptionBudgetStore: rootDir or file is required');
|
|
82
|
+
this.rootDir = rootDir ?? null;
|
|
83
|
+
this.env = env;
|
|
84
|
+
this._storePath = file || budgetStorePath(rootDir);
|
|
85
|
+
this._data = readStoreFile(this._storePath);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_key(actor, runId) {
|
|
89
|
+
return `${actor}::${runId}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_save() {
|
|
93
|
+
try {
|
|
94
|
+
fs.mkdirSync(path.dirname(this._storePath), { recursive: true });
|
|
95
|
+
fs.writeFileSync(this._storePath, JSON.stringify(this._data), 'utf8');
|
|
96
|
+
} catch { /* best-effort, mirrors BrokerStore.save */ }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Resolves the effective budget: an explicit override, else the deployment-mode default. */
|
|
100
|
+
resolveBudget(budgetOverride) {
|
|
101
|
+
if (budgetOverride !== undefined) return budgetOverride;
|
|
102
|
+
const mode = getDeploymentMode(this.env, { cwd: this.rootDir ?? process.cwd() });
|
|
103
|
+
return resolveDefaultBudget(mode);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Cumulative consumption recorded so far for actor+run, zeroed if none. */
|
|
107
|
+
consumptionFor(actor, runId) {
|
|
108
|
+
return { ...emptyConsumption(), ...(this._data[this._key(actor, runId)] || {}) };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* check(actor, runId, kind, amount, { budget }) — would recording `amount`
|
|
113
|
+
* more of `kind` exceed the budget? Read-only, no state mutated.
|
|
114
|
+
*/
|
|
115
|
+
check(actor, runId, kind, amount = 1, { budget } = {}) {
|
|
116
|
+
const resolved = this.resolveBudget(budget);
|
|
117
|
+
const consumption = this.consumptionFor(actor, runId);
|
|
118
|
+
if (!resolved) return { allowed: true, cap: null, projected: consumption[kind] + amount, consumption };
|
|
119
|
+
const cap = resolved[kind];
|
|
120
|
+
if (!Number.isFinite(cap)) return { allowed: true, cap: null, projected: consumption[kind] + amount, consumption };
|
|
121
|
+
const projected = consumption[kind] + amount;
|
|
122
|
+
return { allowed: projected <= cap, cap, projected, consumption };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Durably record `amount` more of `kind` for actor+run. Returns the updated row. */
|
|
126
|
+
record(actor, runId, kind, amount = 1) {
|
|
127
|
+
const key = this._key(actor, runId);
|
|
128
|
+
const current = { ...emptyConsumption(), ...(this._data[key] || {}) };
|
|
129
|
+
current[kind] = (current[kind] || 0) + amount;
|
|
130
|
+
this._data[key] = current;
|
|
131
|
+
this._save();
|
|
132
|
+
return current;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Every actor+run row with its resolved budget, for status surfacing. */
|
|
136
|
+
allEntries() {
|
|
137
|
+
return Object.entries(this._data).map(([key, consumption]) => {
|
|
138
|
+
const sep = key.indexOf('::');
|
|
139
|
+
const actor = sep === -1 ? key : key.slice(0, sep);
|
|
140
|
+
const runId = sep === -1 ? null : key.slice(sep + 2);
|
|
141
|
+
return {
|
|
142
|
+
actor,
|
|
143
|
+
runId,
|
|
144
|
+
consumption: { ...emptyConsumption(), ...consumption },
|
|
145
|
+
budget: this.resolveBudget(undefined),
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|