@geraldmaron/construct 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
package/lib/output-quality.mjs
CHANGED
|
@@ -8,8 +8,17 @@
|
|
|
8
8
|
* screenshot evidence recorded. Every check degrades with a typed reason when its tool is absent
|
|
9
9
|
* rather than failing the publish; only a real, document-scoped validity failure (a zero-page PDF,
|
|
10
10
|
* dropped content, a missing local reference) marks the result not-ok.
|
|
11
|
+
*
|
|
12
|
+
* Markup visual gate (HTML only): invisible text (opacity:0, matched fg/bg color) and hard-overlapping
|
|
13
|
+
* absolutely-positioned elements are detected from the produced file's own markup, closing the blind
|
|
14
|
+
* spot where the text roundtrip passes because hidden glyphs are still extractable.
|
|
15
|
+
*
|
|
16
|
+
* Render-smoke hard-fail: for pdf and pptx deliverables at render-smoke+, a missing required renderer
|
|
17
|
+
* is a hard failure rather than a graceful degrade — a user inspecting output before claiming done
|
|
18
|
+
* cannot accept a quality claim that rests on a tool that was never invoked.
|
|
11
19
|
*/
|
|
12
20
|
|
|
21
|
+
import fs from 'node:fs';
|
|
13
22
|
import { validatePdf, contentRoundtrip, referenceIntegrity } from './export-validate.mjs';
|
|
14
23
|
import { validateBrandContrast } from './brand-contrast.mjs';
|
|
15
24
|
import { captureRenderEvidence } from './render-evidence.mjs';
|
|
@@ -18,6 +27,49 @@ import { auditAccessibility } from './a11y-audit.mjs';
|
|
|
18
27
|
|
|
19
28
|
const RENDER_GATE_LEVELS = new Set(['render-smoke', 'full-certification', 'human-reviewed']);
|
|
20
29
|
|
|
30
|
+
// PDF and PPTX require external renderers; their absence at render-smoke+ is a hard failure.
|
|
31
|
+
const RENDER_REQUIRED_FORMATS = new Set(['pdf', 'pptx']);
|
|
32
|
+
|
|
33
|
+
// Detect invisible and hard-overlapping text in a produced HTML deliverable. The text roundtrip
|
|
34
|
+
// passes even when body text is invisible because hidden glyphs are still extractable; this gate
|
|
35
|
+
// catches opacity:0, matched foreground/background color, and hard-stacked absolute elements.
|
|
36
|
+
|
|
37
|
+
function markupVisualGate(exportPath, format) {
|
|
38
|
+
if (format !== 'html') return { ok: true, issues: [], degradation: null };
|
|
39
|
+
let html;
|
|
40
|
+
try { html = fs.readFileSync(exportPath, 'utf8'); } catch { return { ok: true, issues: [], degradation: null }; }
|
|
41
|
+
|
|
42
|
+
const issues = [];
|
|
43
|
+
|
|
44
|
+
if (/\bopacity\s*:\s*0(?!\.\d)/i.test(html)) {
|
|
45
|
+
issues.push('invisible-text: opacity:0 on element');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const m of html.matchAll(/style\s*=\s*["']([^"']*)["']/gi)) {
|
|
49
|
+
const style = m[1];
|
|
50
|
+
const fgM = style.match(/\bcolor\s*:\s*(#[0-9a-f]{3,6}|[a-z]+)/i);
|
|
51
|
+
const bgM = style.match(/\bbackground(?:-color)?\s*:\s*(#[0-9a-f]{3,6}|[a-z]+)/i);
|
|
52
|
+
if (fgM && bgM && fgM[1].toLowerCase() === bgM[1].toLowerCase()) {
|
|
53
|
+
issues.push(`invisible-text: foreground and background both "${fgM[1].toLowerCase()}"`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const positions = new Map();
|
|
58
|
+
for (const m of html.matchAll(/style\s*=\s*["']([^"']*)["']/gi)) {
|
|
59
|
+
const style = m[1];
|
|
60
|
+
if (!/position\s*:\s*absolute/i.test(style)) continue;
|
|
61
|
+
const top = (style.match(/\btop\s*:\s*([^;,"'\s]+)/) || [])[1] ?? '';
|
|
62
|
+
const left = (style.match(/\bleft\s*:\s*([^;,"'\s]+)/) || [])[1] ?? '';
|
|
63
|
+
if (top === '' && left === '') continue;
|
|
64
|
+
const key = `${top}|${left}`;
|
|
65
|
+
const cnt = (positions.get(key) || 0) + 1;
|
|
66
|
+
positions.set(key, cnt);
|
|
67
|
+
if (cnt === 2) issues.push(`overlapping-text: elements stacked at top:${top} left:${left}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { ok: issues.length === 0, issues, degradation: null };
|
|
71
|
+
}
|
|
72
|
+
|
|
21
73
|
// render-pipeline keys an export format to an image renderer; deck is HTML under the hood. A
|
|
22
74
|
// format with no renderer skips the screenshot capture rather than guessing one.
|
|
23
75
|
|
|
@@ -42,7 +94,8 @@ export function runOutputQuality({
|
|
|
42
94
|
|
|
43
95
|
if (format === 'pdf') checks.pdf = validatePdf(exportPath, env);
|
|
44
96
|
checks.roundtrip = contentRoundtrip({ exportPath, format, sourceMarkdown, env });
|
|
45
|
-
if (sourceMarkdown && baseDir) checks.references = referenceIntegrity(sourceMarkdown, baseDir);
|
|
97
|
+
if (sourceMarkdown && baseDir) checks.references = referenceIntegrity(sourceMarkdown, baseDir, exportPath);
|
|
98
|
+
if (format === 'html') checks.markup = markupVisualGate(exportPath, format);
|
|
46
99
|
|
|
47
100
|
// Diagram legibility is advisory by default; `cx_diagram_quality: strict` escalates it.
|
|
48
101
|
const diagrams = lintDocumentDiagrams(sourceMarkdown);
|
|
@@ -62,12 +115,18 @@ export function runOutputQuality({
|
|
|
62
115
|
}
|
|
63
116
|
|
|
64
117
|
// A typed degradation (tool absent) never fails the publish; only a non-degraded,
|
|
65
|
-
// document-scoped check whose ok is explicitly false does.
|
|
118
|
+
// document-scoped check whose ok is explicitly false does. Exception: at render-smoke+ gate
|
|
119
|
+
// levels, a missing required renderer for pdf/pptx is a hard failure — the quality claim depends
|
|
120
|
+
// on a tool that was never invoked.
|
|
66
121
|
|
|
67
122
|
const failures = Object.entries(checks)
|
|
68
123
|
.filter(([, result]) => result && result.ok === false && !result.degradation)
|
|
69
124
|
.map(([name, result]) => `${name}: ${result.message || 'failed'}`);
|
|
70
125
|
|
|
126
|
+
if (RENDER_GATE_LEVELS.has(gateLevel) && RENDER_REQUIRED_FORMATS.has(format) && render?.result?.degradation === 'missing-dependency') {
|
|
127
|
+
failures.push(`render: required ${format} renderer absent at gate level '${gateLevel}'`);
|
|
128
|
+
}
|
|
129
|
+
|
|
71
130
|
if (diagramStrict && !diagrams.ok) {
|
|
72
131
|
for (const w of diagrams.warnings) failures.push(`diagram ${w.diagram} (${w.kind}): ${w.code}`);
|
|
73
132
|
}
|
|
@@ -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';
|