@geraldmaron/construct 1.4.2 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +57 -45
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +273 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +104 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/cli.mjs — `construct pack` command surface (LMCP-E3).
|
|
3
|
+
*
|
|
4
|
+
* Subcommands:
|
|
5
|
+
* list Every pack discovered across builtin/user/project tiers
|
|
6
|
+
* (loader.mjs precedence merge), each annotated with its
|
|
7
|
+
* durable enabled state (enablement.mjs).
|
|
8
|
+
* enable <id> Validate the pack's on-disk manifest and record it as
|
|
9
|
+
* enabled in .cx/packs.json. Refuses — naming the exact
|
|
10
|
+
* validation error — on an incompatible compatVersion or
|
|
11
|
+
* any other manifest defect.
|
|
12
|
+
* disable <id> Remove the pack's enabled entry. Idempotent. Refuses to
|
|
13
|
+
* disable the core pack.
|
|
14
|
+
* info <id> Full manifest plus enabled state for one pack.
|
|
15
|
+
*
|
|
16
|
+
* `--json` on every subcommand emits a machine-readable payload; the text
|
|
17
|
+
* form is the human-readable default.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { loadAllPacks } from './loader.mjs';
|
|
21
|
+
import { readEnablementState, isEnabled, enablePack, disablePack } from './enablement.mjs';
|
|
22
|
+
|
|
23
|
+
function annotatedPacks({ rootDir, homeDir, packageRoot, env, deploymentMode }) {
|
|
24
|
+
const { packs, errors } = loadAllPacks({ rootDir, homeDir, packageRoot, env, deploymentMode });
|
|
25
|
+
const state = readEnablementState(rootDir);
|
|
26
|
+
const annotated = packs.map((p) => ({
|
|
27
|
+
id: p.id,
|
|
28
|
+
version: p.version,
|
|
29
|
+
tier: p._tier,
|
|
30
|
+
enabled: isEnabled(p.id, state, { packageRoot }),
|
|
31
|
+
manifestPath: p._manifestPath || p._packDir || null,
|
|
32
|
+
}));
|
|
33
|
+
return { packs: annotated, errors, state };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function runList({ rootDir, packageRoot, json }) {
|
|
37
|
+
const { packs, errors } = annotatedPacks({ rootDir, packageRoot });
|
|
38
|
+
if (json) {
|
|
39
|
+
process.stdout.write(JSON.stringify({ packs, errors }, null, 2) + '\n');
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(`Packs (${packs.length}):\n`);
|
|
43
|
+
for (const p of packs) {
|
|
44
|
+
process.stdout.write(` ${p.enabled ? '●' : '○'} ${p.id}@${p.version} [${p.tier}]${p.enabled ? ' (enabled)' : ''}\n`);
|
|
45
|
+
}
|
|
46
|
+
for (const e of errors) process.stdout.write(` ! ${e}\n`);
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Splits a `<pack-id>[@version]` spec on the last '@', so a scoped id like
|
|
52
|
+
* '@fixture/sample' (no version) stays intact while '@fixture/sample@2.0.0'
|
|
53
|
+
* splits into id '@fixture/sample' and version '2.0.0'.
|
|
54
|
+
*/
|
|
55
|
+
function parsePackSpec(spec) {
|
|
56
|
+
const at = spec.lastIndexOf('@');
|
|
57
|
+
if (at <= 0) return { packId: spec, requestedVersion: undefined };
|
|
58
|
+
return { packId: spec.slice(0, at), requestedVersion: spec.slice(at + 1) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function runEnable(args, { rootDir, packageRoot, json }) {
|
|
62
|
+
const id = args.find((a) => !a.startsWith('--'));
|
|
63
|
+
if (!id) {
|
|
64
|
+
process.stderr.write('Usage: construct pack enable <pack-id>[@version] [--json]\n');
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
const { packId, requestedVersion } = parsePackSpec(id);
|
|
68
|
+
|
|
69
|
+
const result = enablePack(packId, { rootDir, packageRoot, requestedVersion });
|
|
70
|
+
if (json) {
|
|
71
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
72
|
+
return result.ok ? 0 : 1;
|
|
73
|
+
}
|
|
74
|
+
if (!result.ok) {
|
|
75
|
+
process.stderr.write(`✖ ${result.error}\n`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
process.stdout.write(result.alreadyCore ? `✓ ${packId} is the core pack (always enabled)\n` : `✓ enabled ${packId}@${result.pack.version} [${result.tier}]\n`);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function runDisable(args, { rootDir, packageRoot, json }) {
|
|
83
|
+
const id = args.find((a) => !a.startsWith('--'));
|
|
84
|
+
if (!id) {
|
|
85
|
+
process.stderr.write('Usage: construct pack disable <pack-id> [--json]\n');
|
|
86
|
+
return 1;
|
|
87
|
+
}
|
|
88
|
+
const result = disablePack(id, { rootDir, packageRoot });
|
|
89
|
+
if (json) {
|
|
90
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
91
|
+
return result.ok ? 0 : 1;
|
|
92
|
+
}
|
|
93
|
+
if (!result.ok) {
|
|
94
|
+
process.stderr.write(`✖ ${result.error}\n`);
|
|
95
|
+
return 1;
|
|
96
|
+
}
|
|
97
|
+
process.stdout.write(result.wasEnabled ? `✓ disabled ${id}\n` : `${id} was not enabled (no-op)\n`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function runInfo(args, { rootDir, packageRoot, json }) {
|
|
102
|
+
const id = args.find((a) => !a.startsWith('--'));
|
|
103
|
+
if (!id) {
|
|
104
|
+
process.stderr.write('Usage: construct pack info <pack-id> [--json]\n');
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
const { packs: rawPacks } = loadAllPacks({ rootDir, packageRoot });
|
|
108
|
+
const pack = rawPacks.find((p) => p.id === id);
|
|
109
|
+
if (!pack) {
|
|
110
|
+
process.stderr.write(`pack not found: ${id}\n`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
const state = readEnablementState(rootDir);
|
|
114
|
+
const enabled = isEnabled(id, state, { packageRoot });
|
|
115
|
+
const result = { ...pack, enabled, enabledDetail: state.enabled[id] || null };
|
|
116
|
+
if (json) {
|
|
117
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
process.stdout.write(`${pack.id}@${pack.version} [${pack._tier}] ${enabled ? '(enabled)' : '(disabled)'}\n`);
|
|
121
|
+
if (pack.specialists) process.stdout.write(` specialists: ${Object.keys(pack.specialists).length}\n`);
|
|
122
|
+
if (pack.prompts) process.stdout.write(` prompts: ${Object.keys(pack.prompts).length}\n`);
|
|
123
|
+
if (pack.frameworks) process.stdout.write(` frameworks: ${Object.keys(pack.frameworks).length}\n`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* @param {string[]} args
|
|
129
|
+
* @param {{ rootDir: string, packageRoot?: string }} ctx
|
|
130
|
+
* @returns {number} exit code
|
|
131
|
+
*/
|
|
132
|
+
export function runPackCli(args, { rootDir, packageRoot } = {}) {
|
|
133
|
+
const sub = args[0];
|
|
134
|
+
const rest = args.slice(1);
|
|
135
|
+
const json = args.includes('--json');
|
|
136
|
+
|
|
137
|
+
if (sub === 'list') return runList({ rootDir, packageRoot, json });
|
|
138
|
+
if (sub === 'enable') return runEnable(rest, { rootDir, packageRoot, json });
|
|
139
|
+
if (sub === 'disable') return runDisable(rest, { rootDir, packageRoot, json });
|
|
140
|
+
if (sub === 'info') return runInfo(rest, { rootDir, packageRoot, json });
|
|
141
|
+
|
|
142
|
+
process.stderr.write(`Unknown pack subcommand: ${sub}. Available: list, enable, disable, info\n`);
|
|
143
|
+
return 1;
|
|
144
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/core-pack.mjs — programmatic builtin core pack loader.
|
|
3
|
+
*
|
|
4
|
+
* Reads specialists/org/ directory structure and wraps it as the
|
|
5
|
+
* @construct/core pack. This is the builtin tier pack that provides
|
|
6
|
+
* all default specialists, teams, prompts, and reasoning frameworks
|
|
7
|
+
* (ADR-0062) shipped with Construct.
|
|
8
|
+
*
|
|
9
|
+
* `embedBindings` (LMCP-E4) ships default per-specialist provider read/
|
|
10
|
+
* search grants and write-proposal grants for the built-in specialists most
|
|
11
|
+
* likely to run embedded: cx-product-manager, cx-operations, cx-engineer.
|
|
12
|
+
* These are defaults only — a project may override them via a project-tier
|
|
13
|
+
* pack (.cx/packs) under the same specialist id.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, readdirSync, existsSync } from 'node:fs';
|
|
17
|
+
import { join, dirname, resolve } from 'node:path';
|
|
18
|
+
import { fileURLToPath } from 'node:url';
|
|
19
|
+
import yaml from 'js-yaml';
|
|
20
|
+
|
|
21
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
23
|
+
|
|
24
|
+
const FRONTMATTER_RE = /^---\n([\s\S]*?)\n---\n?/;
|
|
25
|
+
|
|
26
|
+
// Default per-specialist embed bindings (LMCP-E4). Read/search only — no
|
|
27
|
+
// specialist gets an autonomous write; `proposals` names the write kinds a
|
|
28
|
+
// specialist may propose, and every proposal still passes through
|
|
29
|
+
// AuthorityGuard's approval-queued path before anything executes.
|
|
30
|
+
const DEFAULT_EMBED_BINDINGS = Object.freeze({
|
|
31
|
+
'cx-product-manager': {
|
|
32
|
+
providers: [
|
|
33
|
+
{ id: 'atlassian-jira', capabilities: ['read', 'search'] },
|
|
34
|
+
{ id: 'atlassian-confluence', capabilities: ['read', 'search'] },
|
|
35
|
+
],
|
|
36
|
+
proposals: ['atlassian-jira.createIssue'],
|
|
37
|
+
},
|
|
38
|
+
'cx-operations': {
|
|
39
|
+
providers: [
|
|
40
|
+
{ id: 'atlassian-jira', capabilities: ['read', 'search'] },
|
|
41
|
+
{ id: 'atlassian-confluence', capabilities: ['read', 'search'] },
|
|
42
|
+
{ id: 'slack', capabilities: ['read', 'search'] },
|
|
43
|
+
],
|
|
44
|
+
proposals: ['atlassian-jira.createIssue', 'slack.postMessage'],
|
|
45
|
+
},
|
|
46
|
+
'cx-engineer': {
|
|
47
|
+
providers: [
|
|
48
|
+
{ id: 'github', capabilities: ['read', 'search'] },
|
|
49
|
+
{ id: 'atlassian-jira', capabilities: ['read', 'search'] },
|
|
50
|
+
],
|
|
51
|
+
proposals: ['github.createIssue'],
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export function loadCorePack(rootDir = PACKAGE_ROOT) {
|
|
56
|
+
const orgDir = join(rootDir, 'specialists', 'org');
|
|
57
|
+
const specialistDir = join(orgDir, 'specialists');
|
|
58
|
+
const teamDir = join(orgDir, 'teams');
|
|
59
|
+
const frameworkDir = join(orgDir, 'frameworks');
|
|
60
|
+
|
|
61
|
+
const specialists = existsSync(specialistDir)
|
|
62
|
+
? readdirSync(specialistDir).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''))
|
|
63
|
+
: [];
|
|
64
|
+
|
|
65
|
+
const teams = existsSync(teamDir)
|
|
66
|
+
? readdirSync(teamDir).filter(f => f.endsWith('.json')).map(f => f.replace(/\.json$/, ''))
|
|
67
|
+
: [];
|
|
68
|
+
|
|
69
|
+
const prompts = {};
|
|
70
|
+
for (const specId of specialists) {
|
|
71
|
+
try {
|
|
72
|
+
const spec = JSON.parse(readFileSync(join(specialistDir, `${specId}.json`), 'utf8'));
|
|
73
|
+
if (spec.promptFile) prompts[specId] = spec.promptFile;
|
|
74
|
+
} catch {}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// frameworks map keys by frontmatter `id` (not filename) so a project-tier
|
|
78
|
+
// override in .cx/packs can declare the same id from a differently named
|
|
79
|
+
// file and still win under mergePackTiers/resolveFramework precedence.
|
|
80
|
+
const frameworks = {};
|
|
81
|
+
if (existsSync(frameworkDir)) {
|
|
82
|
+
for (const file of readdirSync(frameworkDir).filter(f => f.endsWith('.md'))) {
|
|
83
|
+
try {
|
|
84
|
+
const raw = readFileSync(join(frameworkDir, file), 'utf8');
|
|
85
|
+
const match = raw.match(FRONTMATTER_RE);
|
|
86
|
+
const fm = match ? yaml.load(match[1]) : null;
|
|
87
|
+
if (fm?.id) frameworks[fm.id] = join('specialists', 'org', 'frameworks', file);
|
|
88
|
+
} catch {}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Only bind specialists actually present on disk — a stripped-down or
|
|
93
|
+
// customized org directory should not surface bindings for personas it
|
|
94
|
+
// doesn't ship.
|
|
95
|
+
const embedBindings = {};
|
|
96
|
+
for (const [specId, binding] of Object.entries(DEFAULT_EMBED_BINDINGS)) {
|
|
97
|
+
if (specialists.includes(specId)) embedBindings[specId] = binding;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id: '@construct/core',
|
|
102
|
+
version: '0.0.0',
|
|
103
|
+
compatVersion: 1,
|
|
104
|
+
teams,
|
|
105
|
+
specialists,
|
|
106
|
+
prompts,
|
|
107
|
+
embedBindings,
|
|
108
|
+
frameworks,
|
|
109
|
+
_tier: 'builtin',
|
|
110
|
+
_sourceDir: orgDir,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/enablement.mjs — durable pack enable/disable lifecycle (LMCP-E3).
|
|
3
|
+
*
|
|
4
|
+
* `loadAllPacks` (loader.mjs) merges every pack it finds on disk across the
|
|
5
|
+
* builtin/user/project tiers unconditionally — correct for authoring-time
|
|
6
|
+
* precedence resolution, but it means dropping a pack manifest into
|
|
7
|
+
* `.cx/packs/` makes it "configured" and already influencing prompt/framework
|
|
8
|
+
* resolution with no explicit operator action (the OTel lesson this bead is
|
|
9
|
+
* named for: configured is not enabled). This module adds an explicit,
|
|
10
|
+
* durable enablement layer on top without changing loadAllPacks' own
|
|
11
|
+
* semantics — existing callers merging all discovered packs (precedence
|
|
12
|
+
* tests, `construct doctor`) are unaffected; a caller that wants
|
|
13
|
+
* enablement-gated packs calls `loadEnabledPacks` instead.
|
|
14
|
+
*
|
|
15
|
+
* State lives at `<rootDir>/.cx/packs.json`:
|
|
16
|
+
* { "version": 1, "enabled": { "<packId>": { "version": "1.0.0", "enabledAt": "<ISO>", "tier": "project" } } }
|
|
17
|
+
*
|
|
18
|
+
* `@construct/core` (the programmatically-injected builtin foundation pack,
|
|
19
|
+
* see core-pack.mjs) is always enabled — it is not optional and carries no
|
|
20
|
+
* manifest file to enable/disable.
|
|
21
|
+
*
|
|
22
|
+
* `enablePack` refuses to enable a pack whose manifest fails validation —
|
|
23
|
+
* including a compatVersion that exceeds this runtime's PACK_COMPAT_VERSION —
|
|
24
|
+
* naming the exact validation error rather than half-enabling it.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
28
|
+
import { dirname, join } from 'node:path';
|
|
29
|
+
|
|
30
|
+
import { loadAllPacks, resolvePackDirs } from './loader.mjs';
|
|
31
|
+
import { validatePackManifest } from './validate.mjs';
|
|
32
|
+
import { loadCorePack } from './core-pack.mjs';
|
|
33
|
+
|
|
34
|
+
const STATE_VERSION = 1;
|
|
35
|
+
|
|
36
|
+
export function resolveEnablementStatePath(rootDir = process.cwd()) {
|
|
37
|
+
return join(rootDir, '.cx', 'packs.json');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} [rootDir]
|
|
42
|
+
* @returns {{ version: number, enabled: Record<string, {version: string, enabledAt: string, tier: string}> }}
|
|
43
|
+
*/
|
|
44
|
+
export function readEnablementState(rootDir = process.cwd()) {
|
|
45
|
+
const statePath = resolveEnablementStatePath(rootDir);
|
|
46
|
+
if (!existsSync(statePath)) {
|
|
47
|
+
return { version: STATE_VERSION, enabled: {} };
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(readFileSync(statePath, 'utf8'));
|
|
51
|
+
if (!parsed || typeof parsed !== 'object' || typeof parsed.enabled !== 'object' || parsed.enabled === null) {
|
|
52
|
+
return { version: STATE_VERSION, enabled: {} };
|
|
53
|
+
}
|
|
54
|
+
return { version: parsed.version || STATE_VERSION, enabled: parsed.enabled };
|
|
55
|
+
} catch {
|
|
56
|
+
return { version: STATE_VERSION, enabled: {} };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function writeEnablementState(rootDir, state) {
|
|
61
|
+
const statePath = resolveEnablementStatePath(rootDir);
|
|
62
|
+
mkdirSync(dirname(statePath), { recursive: true });
|
|
63
|
+
writeFileSync(statePath, JSON.stringify({ version: state.version || STATE_VERSION, enabled: state.enabled || {} }, null, 2) + '\n', 'utf8');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The core pack ships with Construct and is always enabled — it has no
|
|
68
|
+
* manifest file on disk to toggle and every specialist/framework/prompt it
|
|
69
|
+
* carries is the foundation every deployment mode depends on.
|
|
70
|
+
*/
|
|
71
|
+
export function isCorePackId(packId, packageRoot) {
|
|
72
|
+
return packId === loadCorePack(packageRoot).id;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function isEnabled(packId, state, { packageRoot } = {}) {
|
|
76
|
+
if (isCorePackId(packId, packageRoot)) return true;
|
|
77
|
+
return Boolean(state?.enabled?.[packId]);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Scans the three tier directories (project, user, builtin — highest
|
|
82
|
+
* precedence first) for a `pack.manifest.json` whose parsed `id` matches.
|
|
83
|
+
* Returns the parsed manifest alongside its tier/path even when validation
|
|
84
|
+
* would reject it, so `enablePack` can name the exact validation failure
|
|
85
|
+
* instead of reporting a generic "not found".
|
|
86
|
+
*
|
|
87
|
+
* @returns {{ tier: string, packDir: string, manifestPath: string, parsed: object } | null}
|
|
88
|
+
*/
|
|
89
|
+
function findPackManifest(packId, { rootDir, homeDir } = {}) {
|
|
90
|
+
const dirs = resolvePackDirs({ rootDir, homeDir });
|
|
91
|
+
const tiers = [
|
|
92
|
+
{ tier: 'project', dir: dirs.project },
|
|
93
|
+
{ tier: 'user', dir: dirs.user },
|
|
94
|
+
{ tier: 'builtin', dir: dirs.builtin },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const { tier, dir } of tiers) {
|
|
98
|
+
if (!existsSync(dir)) continue;
|
|
99
|
+
const entries = readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory());
|
|
100
|
+
for (const entry of entries) {
|
|
101
|
+
const packDir = join(dir, entry.name);
|
|
102
|
+
const manifestPath = join(packDir, 'pack.manifest.json');
|
|
103
|
+
if (!existsSync(manifestPath)) continue;
|
|
104
|
+
let parsed;
|
|
105
|
+
try {
|
|
106
|
+
parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (parsed?.id === packId) {
|
|
111
|
+
return { tier, packDir, manifestPath, parsed };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @param {string} packId
|
|
120
|
+
* @param {{ rootDir?: string, homeDir?: string, packageRoot?: string, requestedVersion?: string, knownProviders?: object }} [opts]
|
|
121
|
+
* @returns {{ ok: true, pack: object, tier: string, alreadyCore?: true } | { ok: false, error: string }}
|
|
122
|
+
*/
|
|
123
|
+
export function enablePack(packId, opts = {}) {
|
|
124
|
+
const { rootDir = process.cwd(), homeDir, packageRoot, requestedVersion, knownProviders } = opts;
|
|
125
|
+
|
|
126
|
+
if (isCorePackId(packId, packageRoot)) {
|
|
127
|
+
return { ok: true, alreadyCore: true, pack: { id: packId }, tier: 'builtin' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const found = findPackManifest(packId, { rootDir, homeDir });
|
|
131
|
+
if (!found) {
|
|
132
|
+
const dirs = resolvePackDirs({ rootDir, homeDir });
|
|
133
|
+
return { ok: false, error: `pack '${packId}' not found in any builtin/user/project pack directory (looked under ${dirs.project}, ${dirs.user}, ${dirs.builtin})` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (requestedVersion && found.parsed.version !== requestedVersion) {
|
|
137
|
+
return { ok: false, error: `pack '${packId}' on disk is version ${found.parsed.version}, requested ${requestedVersion}` };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const result = validatePackManifest(found.parsed, { filePath: found.manifestPath, knownProviders });
|
|
141
|
+
if (!result.valid) {
|
|
142
|
+
return { ok: false, error: `pack '${packId}' failed validation: ${result.errors.join('; ')}` };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const state = readEnablementState(rootDir);
|
|
146
|
+
state.enabled[packId] = { version: found.parsed.version, enabledAt: new Date().toISOString(), tier: found.tier };
|
|
147
|
+
writeEnablementState(rootDir, state);
|
|
148
|
+
|
|
149
|
+
return { ok: true, pack: found.parsed, tier: found.tier };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Idempotent — disabling an already-disabled (or never-enabled) pack still
|
|
154
|
+
* returns ok:true. The core pack cannot be disabled (ADR-0055 foundation tier).
|
|
155
|
+
*
|
|
156
|
+
* @param {string} packId
|
|
157
|
+
* @param {{ rootDir?: string, packageRoot?: string }} [opts]
|
|
158
|
+
* @returns {{ ok: true, wasEnabled: boolean } | { ok: false, error: string }}
|
|
159
|
+
*/
|
|
160
|
+
export function disablePack(packId, opts = {}) {
|
|
161
|
+
const { rootDir = process.cwd(), packageRoot } = opts;
|
|
162
|
+
if (isCorePackId(packId, packageRoot)) {
|
|
163
|
+
return { ok: false, error: `pack '${packId}' is the core pack and cannot be disabled` };
|
|
164
|
+
}
|
|
165
|
+
const state = readEnablementState(rootDir);
|
|
166
|
+
const wasEnabled = Boolean(state.enabled[packId]);
|
|
167
|
+
delete state.enabled[packId];
|
|
168
|
+
writeEnablementState(rootDir, state);
|
|
169
|
+
return { ok: true, wasEnabled };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Enablement-gated counterpart to `loadAllPacks` — same merge/precedence,
|
|
174
|
+
* filtered to only the core pack plus whatever the durable state marks
|
|
175
|
+
* enabled. Consumers that must not act on a merely-configured-but-unenabled
|
|
176
|
+
* pack (prompt/framework resolution, embed bindings) should call this
|
|
177
|
+
* instead of loadAllPacks directly.
|
|
178
|
+
*
|
|
179
|
+
* @param {object} opts same shape as loadAllPacks' opts
|
|
180
|
+
* @returns {{packs: object[], errors: string[], state: object}}
|
|
181
|
+
*/
|
|
182
|
+
export function loadEnabledPacks(opts = {}) {
|
|
183
|
+
const { packs, errors } = loadAllPacks(opts);
|
|
184
|
+
const state = readEnablementState(opts.rootDir);
|
|
185
|
+
const filtered = packs.filter((p) => isEnabled(p.id, state, { packageRoot: opts.packageRoot }));
|
|
186
|
+
return { packs: filtered, errors, state };
|
|
187
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/index.mjs — public API for the pack manifest system.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all symbols from the five pack sub-modules so consumers
|
|
5
|
+
* can import from a single entry point:
|
|
6
|
+
*
|
|
7
|
+
* import { validatePackManifest, loadAllPacks, PACK_REQUIRED_FIELDS } from './packs/index.mjs';
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export {
|
|
11
|
+
PACK_REQUIRED_FIELDS, PACK_OPTIONAL_FIELDS, PACK_COMPAT_VERSION,
|
|
12
|
+
PACK_ID_RE, PACK_SOURCE_TIERS,
|
|
13
|
+
} from './manifest-schema.mjs';
|
|
14
|
+
|
|
15
|
+
export { validatePackManifest } from './validate.mjs';
|
|
16
|
+
|
|
17
|
+
export { validatePackPrompts, resolvePersonaPrompt } from './prompts.mjs';
|
|
18
|
+
|
|
19
|
+
export { loadCorePack } from './core-pack.mjs';
|
|
20
|
+
|
|
21
|
+
export {
|
|
22
|
+
loadPacksFromDir, mergePackTiers, resolvePackDirs, loadAllPacks,
|
|
23
|
+
} from './loader.mjs';
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/packs/loader.mjs — pack manifest directory loader and merger.
|
|
3
|
+
*
|
|
4
|
+
* Three manifest tiers exist: builtin (shipped with Construct), user (in
|
|
5
|
+
* ~/.config/construct/packs/), and project (in .cx/packs/). Project packs
|
|
6
|
+
* take highest precedence; user packs override builtin. The core pack is
|
|
7
|
+
* programmatically injected at the builtin tier via loadCorePack().
|
|
8
|
+
*
|
|
9
|
+
* `loadPacksFromDir` reads every subdirectory containing pack.manifest.json
|
|
10
|
+
* and validates each one. `mergePackTiers` merges the three tiers by id.
|
|
11
|
+
* `resolvePackDirs` returns the canonical paths for each tier.
|
|
12
|
+
* `loadAllPacks` is a convenience that loads from all tiers.
|
|
13
|
+
*
|
|
14
|
+
* Prompt validation (LMCP-E2): in team/enterprise deployment mode a pack whose
|
|
15
|
+
* declared prompt files are missing or lack valid frontmatter is rejected at
|
|
16
|
+
* load time — the error names the missing file — rather than letting the
|
|
17
|
+
* worker discover the gap mid-run and silently substitute a generic persona.
|
|
18
|
+
* Solo mode does not hard-fail here; the worker records the miss as a visible
|
|
19
|
+
* degraded fallback instead (lib/orchestration/worker.mjs).
|
|
20
|
+
*
|
|
21
|
+
* embedBindings validation (LMCP-E4): every pack's `embedBindings` block is
|
|
22
|
+
* cross-checked against the builtin extension manifests (lib/extensions) so
|
|
23
|
+
* a binding naming an unknown provider id or an undeclared capability fails
|
|
24
|
+
* pack validation with a path, rather than the authority guard discovering
|
|
25
|
+
* the gap at proposal time.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
29
|
+
import { dirname, join, resolve } from 'node:path';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
|
|
32
|
+
import { validatePackManifest } from './validate.mjs';
|
|
33
|
+
import { validatePackPrompts } from './prompts.mjs';
|
|
34
|
+
import { loadCorePack } from './core-pack.mjs';
|
|
35
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
36
|
+
import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
|
|
37
|
+
|
|
38
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
40
|
+
|
|
41
|
+
const PROMPT_HARD_FAIL_MODES = new Set(['team', 'enterprise']);
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load builtin extension manifests keyed by id, for embedBindings
|
|
45
|
+
* cross-validation. Only the builtin tier is consulted — the same set every
|
|
46
|
+
* `construct doctor` / pack loader run sees, independent of project overrides.
|
|
47
|
+
*/
|
|
48
|
+
function loadKnownProviderManifests(packageRoot) {
|
|
49
|
+
const { builtin } = resolveManifestDirs({ rootDir: packageRoot });
|
|
50
|
+
const { manifests } = loadManifestsFromDir(builtin);
|
|
51
|
+
const byId = {};
|
|
52
|
+
for (const m of manifests) byId[m.id] = m;
|
|
53
|
+
return byId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function loadPacksFromDir(packsBaseDir, opts = {}) {
|
|
57
|
+
const packs = [];
|
|
58
|
+
const errors = [];
|
|
59
|
+
|
|
60
|
+
if (!existsSync(packsBaseDir)) {
|
|
61
|
+
return { packs, errors };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let entries;
|
|
65
|
+
try {
|
|
66
|
+
entries = readdirSync(packsBaseDir, { withFileTypes: true });
|
|
67
|
+
} catch (err) {
|
|
68
|
+
errors.push(`${packsBaseDir}: failed to read directory (${err.message})`);
|
|
69
|
+
return { packs, errors };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const packDirs = entries.filter(e => e.isDirectory()).map(e => join(packsBaseDir, e.name)).sort();
|
|
73
|
+
const deploymentMode = opts.deploymentMode || getDeploymentMode(opts.env);
|
|
74
|
+
const packageRoot = opts.packageRoot || PACKAGE_ROOT;
|
|
75
|
+
const knownProviders = opts.knownProviders || loadKnownProviderManifests(packageRoot);
|
|
76
|
+
|
|
77
|
+
for (const packDir of packDirs) {
|
|
78
|
+
const manifestPath = join(packDir, 'pack.manifest.json');
|
|
79
|
+
if (!existsSync(manifestPath)) continue;
|
|
80
|
+
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
84
|
+
} catch (err) {
|
|
85
|
+
errors.push(`${manifestPath}: failed to parse JSON (${err.message})`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const result = validatePackManifest(parsed, { filePath: manifestPath, strict: opts.strict, knownProviders });
|
|
90
|
+
if (!result.valid) {
|
|
91
|
+
errors.push(...result.errors);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (PROMPT_HARD_FAIL_MODES.has(deploymentMode)) {
|
|
96
|
+
const promptResult = validatePackPrompts({ ...parsed, _packDir: packDir }, { packageRoot });
|
|
97
|
+
if (!promptResult.valid) {
|
|
98
|
+
errors.push(...promptResult.errors.map((e) => `${manifestPath}: ${e}`));
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
packs.push({ ...parsed, _tier: opts.tier || 'unknown', _manifestPath: manifestPath, _packDir: packDir });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { packs, errors };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function mergePackTiers(builtin = [], user = [], project = []) {
|
|
110
|
+
const map = new Map();
|
|
111
|
+
|
|
112
|
+
for (const p of builtin) map.set(p.id, p);
|
|
113
|
+
for (const p of user) map.set(p.id, p);
|
|
114
|
+
for (const p of project) map.set(p.id, p);
|
|
115
|
+
|
|
116
|
+
return [...map.values()];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function resolvePackDirs({ rootDir, homeDir } = {}) {
|
|
120
|
+
const home = homeDir ?? (process.env.HOME || process.env.USERPROFILE || '');
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
builtin: join(PACKAGE_ROOT, 'lib', 'packs', 'manifests'),
|
|
124
|
+
user: join(home, '.config', 'construct', 'packs'),
|
|
125
|
+
project: join(rootDir || process.cwd(), '.cx', 'packs'),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function loadAllPacks(opts = {}) {
|
|
130
|
+
const dirs = resolvePackDirs(opts);
|
|
131
|
+
const allErrors = [];
|
|
132
|
+
const deploymentMode = opts.deploymentMode || getDeploymentMode(opts.env);
|
|
133
|
+
const packageRoot = opts.packageRoot || PACKAGE_ROOT;
|
|
134
|
+
const knownProviders = opts.knownProviders || loadKnownProviderManifests(packageRoot);
|
|
135
|
+
|
|
136
|
+
const builtinResult = loadPacksFromDir(dirs.builtin, { ...opts, tier: 'builtin', deploymentMode, packageRoot, knownProviders });
|
|
137
|
+
allErrors.push(...builtinResult.errors);
|
|
138
|
+
|
|
139
|
+
const userResult = loadPacksFromDir(dirs.user, { ...opts, tier: 'user', deploymentMode, packageRoot, knownProviders });
|
|
140
|
+
allErrors.push(...userResult.errors);
|
|
141
|
+
|
|
142
|
+
const projectResult = loadPacksFromDir(dirs.project, { ...opts, tier: 'project', deploymentMode, packageRoot, knownProviders });
|
|
143
|
+
allErrors.push(...projectResult.errors);
|
|
144
|
+
|
|
145
|
+
const corePack = loadCorePack(packageRoot);
|
|
146
|
+
let builtinPacks = [...builtinResult.packs, corePack];
|
|
147
|
+
|
|
148
|
+
// The core pack ships with Construct and is not read from a manifest file,
|
|
149
|
+
// bypassing loadPacksFromDir's validation loop above; the same hard-fail
|
|
150
|
+
// check still applies under team/enterprise, naming the missing file. The
|
|
151
|
+
// same is true of embedBindings — validated explicitly here so an unknown
|
|
152
|
+
// provider id or undeclared capability in the core pack's own bindings
|
|
153
|
+
// fails loudly instead of silently granting excess authority.
|
|
154
|
+
if (PROMPT_HARD_FAIL_MODES.has(deploymentMode)) {
|
|
155
|
+
const coreResult = validatePackPrompts(corePack, { packageRoot });
|
|
156
|
+
if (!coreResult.valid) {
|
|
157
|
+
allErrors.push(...coreResult.errors.map((e) => `${corePack._sourceDir || '@construct/core'}: ${e}`));
|
|
158
|
+
builtinPacks = builtinResult.packs;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (corePack.embedBindings) {
|
|
163
|
+
const coreBindingResult = validatePackManifest(
|
|
164
|
+
{ id: corePack.id, version: corePack.version, compatVersion: corePack.compatVersion, embedBindings: corePack.embedBindings },
|
|
165
|
+
{ filePath: corePack._sourceDir || '@construct/core', knownProviders }
|
|
166
|
+
);
|
|
167
|
+
if (!coreBindingResult.valid) {
|
|
168
|
+
allErrors.push(...coreBindingResult.errors);
|
|
169
|
+
builtinPacks = builtinPacks.filter((p) => p.id !== corePack.id);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const merged = mergePackTiers(builtinPacks, userResult.packs, projectResult.packs);
|
|
174
|
+
|
|
175
|
+
return { packs: merged, errors: allErrors };
|
|
176
|
+
}
|