@geraldmaron/construct 1.4.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1027 -64
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +6 -29
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +89 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +18 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +50 -9
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +19 -7
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +154 -1411
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +1 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +165 -25
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +0 -8
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +31 -7
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +446 -39
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +3 -2
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +528 -89
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +6 -0
- package/lib/providers/secret-resolver.mjs +240 -23
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +8 -3
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +38 -19
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +77 -5
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +16 -8
- package/personas/construct.md +12 -3
- package/registry/capabilities.json +143 -36
- package/rules/common/comments.md +1 -0
- package/schemas/project-config.schema.json +0 -12
- package/schemas/unified-registry.schema.json +2 -4
- package/scripts/sync-specialists.mjs +284 -81
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +40 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +25 -30
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
|
@@ -9,10 +9,28 @@
|
|
|
9
9
|
* 3. Runtime handoff: a single artifact validated against the schema referenced
|
|
10
10
|
* by a producer→consumer contract, with mustContain post-conditions.
|
|
11
11
|
*
|
|
12
|
+
* A contract's `postconditions` array is classified per-entry via
|
|
13
|
+
* `postconditionType` ('executable' | 'advisory' — see construct-rf26.12).
|
|
14
|
+
* Four real enforcement mechanisms back the executable ones:
|
|
15
|
+
* - `contract.output.mustContain` / `mustContainOneOf` / `mustMatchEnum` —
|
|
16
|
+
* declarative field-presence and enum checks against the handoff artifact.
|
|
17
|
+
* - `contract.output.schema` — recursive required+enum validation against
|
|
18
|
+
* the referenced lib/contract-schemas/*.json (not just top-level required).
|
|
19
|
+
* - `contract.postconditions[].check` — structured checks against the
|
|
20
|
+
* on-disk artifact (see validateArtifactPostconditions), for markdown
|
|
21
|
+
* deliverables (sections, citations, tables).
|
|
22
|
+
* - lib/specialists/postconditions.mjs POSTCONDITIONS table — binary,
|
|
23
|
+
* producer-keyed packet invariants, referenced from a postcondition via
|
|
24
|
+
* `enforcedVia: 'binary-postcondition'`.
|
|
25
|
+
* Advisory entries are prose that no mechanism above can decide (subjective
|
|
26
|
+
* quality, cross-document consistency, sequencing/process guarantees) —
|
|
27
|
+
* documented, not silently "enforced."
|
|
28
|
+
*
|
|
12
29
|
* Surfaces:
|
|
13
30
|
* - scripts/sync-specialists.mjs invokes validateContractsFile at sync time.
|
|
14
31
|
* - bin/construct lint:contracts invokes the same path in CI.
|
|
15
32
|
* - workflowContractValidate (runtime) invokes validateHandoff per handoff.
|
|
33
|
+
* - lib/contracts/coverage.mjs reports executable-vs-advisory coverage.
|
|
16
34
|
*/
|
|
17
35
|
|
|
18
36
|
import { existsSync, readFileSync } from 'node:fs';
|
|
@@ -238,11 +256,12 @@ export function validateHandoff(opts = {}) {
|
|
|
238
256
|
}
|
|
239
257
|
}
|
|
240
258
|
|
|
241
|
-
// A handoff carries a producer's output, which is the consumer's input
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
259
|
+
// A handoff carries a producer's output, which is the consumer's input: the
|
|
260
|
+
// same physical packet, checked from both ends. `input.mustContain` is what
|
|
261
|
+
// the consumer expects to receive; `output.mustContain` / `mustContainOneOf` /
|
|
262
|
+
// `mustMatchEnum` is what the producer is contractually required to have put
|
|
263
|
+
// there. Both apply to the same `artifact` value — there is no separate
|
|
264
|
+
// "producer's copy" to check.
|
|
246
265
|
const inputMustContain = contract.input?.mustContain || [];
|
|
247
266
|
for (const field of inputMustContain) {
|
|
248
267
|
if (!hasField(artifact, field)) {
|
|
@@ -250,6 +269,36 @@ export function validateHandoff(opts = {}) {
|
|
|
250
269
|
}
|
|
251
270
|
}
|
|
252
271
|
|
|
272
|
+
const outputSpec = contract.output || {};
|
|
273
|
+
for (const field of outputSpec.mustContain || []) {
|
|
274
|
+
if (!isPresentValue(artifact?.[field])) {
|
|
275
|
+
errors.push(`artifact missing required output field: ${field}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const group of outputSpec.mustContainOneOf || []) {
|
|
279
|
+
if (!Array.isArray(group) || group.length === 0) continue;
|
|
280
|
+
if (!group.some((field) => isPresentValue(artifact?.[field]))) {
|
|
281
|
+
errors.push(`artifact missing one of required output fields: ${group.join('|')}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (outputSpec.mustMatchEnum && typeof outputSpec.mustMatchEnum === 'object') {
|
|
285
|
+
for (const [field, allowed] of Object.entries(outputSpec.mustMatchEnum)) {
|
|
286
|
+
if (!Array.isArray(allowed) || allowed.length === 0) continue;
|
|
287
|
+
const value = artifact?.[field];
|
|
288
|
+
if (value === undefined || value === null) continue;
|
|
289
|
+
if (!allowed.includes(value)) {
|
|
290
|
+
errors.push(`artifact field '${field}' value '${value}' not in allowed set: ${allowed.join(', ')}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Output.schema validation only applies when the artifact declares the
|
|
296
|
+
// matching `type` — several contracts share one schema file (e.g.
|
|
297
|
+
// implementation.json), so `type` disambiguates which schema this
|
|
298
|
+
// particular packet claims to satisfy. Once matched, the check recurses
|
|
299
|
+
// into nested `required` and `enum` constraints (e.g. a `verificationChecklist`
|
|
300
|
+
// sub-object's required booleans, or a `findings[].severity` enum) rather
|
|
301
|
+
// than stopping at top-level required-field presence.
|
|
253
302
|
const outputType = contract.output?.type;
|
|
254
303
|
const schemaRef = contract.output?.schema;
|
|
255
304
|
if (schemaRef && outputType && artifact && artifact.type === outputType) {
|
|
@@ -258,9 +307,7 @@ export function validateHandoff(opts = {}) {
|
|
|
258
307
|
errors.push(`contract output.schema '${schemaRef}' does not exist on disk`);
|
|
259
308
|
} else {
|
|
260
309
|
const schema = readJson(schemaPath);
|
|
261
|
-
|
|
262
|
-
if (!hasField(artifact, field)) errors.push(`artifact missing schema-required field: ${field}`);
|
|
263
|
-
}
|
|
310
|
+
collectSchemaShapeErrors(artifact, schema, 'artifact', errors);
|
|
264
311
|
}
|
|
265
312
|
}
|
|
266
313
|
|
|
@@ -304,6 +351,43 @@ function hasField(obj, field) {
|
|
|
304
351
|
return Object.prototype.hasOwnProperty.call(obj, field) && obj[field] != null && obj[field] !== '';
|
|
305
352
|
}
|
|
306
353
|
|
|
354
|
+
// Stricter than hasField: an empty array counts as "declared but empty", not
|
|
355
|
+
// present. Used for output.mustContain / mustContainOneOf, mirroring
|
|
356
|
+
// lib/specialist-contracts.mjs's validatePacket — a reviewer with
|
|
357
|
+
// findings: [] and no noIssuesFoundAt must not read as "findings present".
|
|
358
|
+
|
|
359
|
+
function isPresentValue(value) {
|
|
360
|
+
if (value === undefined || value === null) return false;
|
|
361
|
+
if (typeof value === 'string' && value.trim() === '') return false;
|
|
362
|
+
if (Array.isArray(value) && value.length === 0) return false;
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Recursively validate `value` against a JSON-schema-shaped `schema`,
|
|
368
|
+
* collecting `required` and `enum` violations (both top-level and nested
|
|
369
|
+
* through `properties` and array `items`). Bounded to the constraint kinds
|
|
370
|
+
* contract output schemas actually use — not a general JSON Schema validator.
|
|
371
|
+
*/
|
|
372
|
+
function collectSchemaShapeErrors(value, schema, pathLabel, errors) {
|
|
373
|
+
if (!schema || typeof schema !== 'object') return;
|
|
374
|
+
if (value === undefined || value === null) return;
|
|
375
|
+
for (const field of schema.required || []) {
|
|
376
|
+
if (!hasField(value, field)) errors.push(`${pathLabel} missing schema-required field: ${field}`);
|
|
377
|
+
}
|
|
378
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
379
|
+
errors.push(`${pathLabel} value '${value}' not in schema enum: ${schema.enum.join(', ')}`);
|
|
380
|
+
}
|
|
381
|
+
if (schema.properties && typeof value === 'object' && !Array.isArray(value)) {
|
|
382
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
383
|
+
if (key in value) collectSchemaShapeErrors(value[key], propSchema, `${pathLabel}.${key}`, errors);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (schema.type === 'array' && schema.items && Array.isArray(value)) {
|
|
387
|
+
value.forEach((item, i) => collectSchemaShapeErrors(item, schema.items, `${pathLabel}[${i}]`, errors));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
307
391
|
/**
|
|
308
392
|
* Validate team boundary enforcement on a contract.
|
|
309
393
|
* Returns array of error strings (empty if validation passes).
|
|
@@ -471,21 +555,26 @@ function claimsHaveCitations(body) {
|
|
|
471
555
|
|
|
472
556
|
/**
|
|
473
557
|
* Run structured postcondition checks against a handoff's output artifact on
|
|
474
|
-
* disk.
|
|
475
|
-
*
|
|
558
|
+
* disk. Every postcondition is classified via `postconditionType`:
|
|
559
|
+
* `executable` entries carry a `check` this function runs; `advisory`
|
|
560
|
+
* entries are documented but deliberately not mechanically checked (see
|
|
561
|
+
* lib/decisions/registry.mjs's contract-decision classification). Bare
|
|
562
|
+
* strings are a legacy shape, tolerated but never executed. Returns an
|
|
563
|
+
* array of error strings.
|
|
476
564
|
*/
|
|
477
565
|
export function validateArtifactPostconditions({ contract, artifactPath, cwd = process.cwd(), rootDir } = {}) {
|
|
478
566
|
if (!contract?.postconditions?.length) return [];
|
|
567
|
+
const executableDeclared = contract.postconditions.filter((p) => typeof p === 'object' && p !== null && p.check);
|
|
479
568
|
if (!artifactPath || !existsSync(artifactPath)) {
|
|
480
|
-
|
|
481
|
-
if (structured.length === 0) return [];
|
|
569
|
+
if (executableDeclared.length === 0) return [];
|
|
482
570
|
return [`postcondition checks declared but artifact not found on disk: ${artifactPath}`];
|
|
483
571
|
}
|
|
484
572
|
const text = readFileSync(artifactPath, 'utf8');
|
|
485
573
|
const { frontmatter, body } = parseArtifactFrontmatter(text);
|
|
486
574
|
const errors = [];
|
|
487
575
|
for (const pc of contract.postconditions) {
|
|
488
|
-
if (typeof pc !== 'object') continue;
|
|
576
|
+
if (typeof pc !== 'object' || pc === null) continue;
|
|
577
|
+
if (pc.postconditionType === 'advisory' || !pc.check) continue;
|
|
489
578
|
const tag = pc.id || pc.check;
|
|
490
579
|
switch (pc.check) {
|
|
491
580
|
case 'artifact-has-frontmatter-field': {
|
|
@@ -100,6 +100,49 @@ function violationMatchesTail(tail, contractId, direction, missing, packet) {
|
|
|
100
100
|
&& JSON.stringify(tailKeys) === JSON.stringify(keys);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
const BARE_GOAL_FIELD_LABELS = ['intent', 'workCategory', 'riskFlags', 'acceptanceCriteria'];
|
|
104
|
+
|
|
105
|
+
const RESEARCHER_PROBE_MISSING = [
|
|
106
|
+
'artifact missing required field: question',
|
|
107
|
+
'artifact missing required field: method',
|
|
108
|
+
'artifact missing required field: sources',
|
|
109
|
+
'artifact missing required field: findings',
|
|
110
|
+
'artifact missing required field: confidence',
|
|
111
|
+
];
|
|
112
|
+
|
|
113
|
+
const A11Y_PROBE_MISSING = [
|
|
114
|
+
'artifact missing required field: findings',
|
|
115
|
+
'artifact missing required field: wcagCriterion',
|
|
116
|
+
'artifact missing required field: userImpact',
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
function missingBareGoalConstructFields(missing) {
|
|
120
|
+
if (!Array.isArray(missing) || missing.length !== BARE_GOAL_FIELD_LABELS.length) return false;
|
|
121
|
+
return BARE_GOAL_FIELD_LABELS.every((field) =>
|
|
122
|
+
missing.some((entry) => String(entry).includes(field)),
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function isBareGoalConstructViolation(row) {
|
|
127
|
+
if (row?.contractId !== 'construct-to-orchestrator') return false;
|
|
128
|
+
if (!missingBareGoalConstructFields(row.missing)) return false;
|
|
129
|
+
const keys = row.packet_keys ?? row.packetKeys;
|
|
130
|
+
return Array.isArray(keys) && keys.length === 1 && keys[0] === 'goal';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isEmptyProbeViolation(row, contractId, expectedMissing) {
|
|
134
|
+
if (row?.contractId !== contractId) return false;
|
|
135
|
+
const keys = row.packet_keys ?? row.packetKeys;
|
|
136
|
+
if (!Array.isArray(keys) || keys.length !== 0) return false;
|
|
137
|
+
return JSON.stringify(row.missing) === JSON.stringify(expectedMissing);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function isDevSessionContractNoise(row) {
|
|
141
|
+
return isBareGoalConstructViolation(row)
|
|
142
|
+
|| isEmptyProbeViolation(row, 'researcher-to-architect', RESEARCHER_PROBE_MISSING)
|
|
143
|
+
|| isEmptyProbeViolation(row, 'accessibility-to-qa', A11Y_PROBE_MISSING);
|
|
144
|
+
}
|
|
145
|
+
|
|
103
146
|
function sha256(input) { return createHash('sha256').update(input).digest('hex'); }
|
|
104
147
|
|
|
105
148
|
function readLastAgent() {
|
|
@@ -160,12 +203,12 @@ export function logViolation(contractId, direction, missing, packet, extra = {})
|
|
|
160
203
|
* Rotated segments are intentionally not included — this is the doctor
|
|
161
204
|
* surface, scoped to the active window.
|
|
162
205
|
*/
|
|
163
|
-
export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot } = {}) {
|
|
206
|
+
export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot, excludeDevNoise = false, limit = Infinity, now = Date.now() } = {}) {
|
|
164
207
|
const file = logFile(repoRoot);
|
|
165
208
|
if (!existsSync(file)) return [];
|
|
166
209
|
const supersedeCutoff = readViolationSupersedeCutoff(repoRoot);
|
|
167
210
|
try {
|
|
168
|
-
const windowCutoff =
|
|
211
|
+
const windowCutoff = now - windowMs;
|
|
169
212
|
return readFileSync(file, 'utf8')
|
|
170
213
|
.trim()
|
|
171
214
|
.split('\n')
|
|
@@ -175,8 +218,11 @@ export function recentViolations({ windowMs = 24 * 60 * 60 * 1000, repoRoot } =
|
|
|
175
218
|
if (!r) return false;
|
|
176
219
|
const ts = new Date(r.ts).getTime();
|
|
177
220
|
if (supersedeCutoff != null && ts <= supersedeCutoff) return false;
|
|
178
|
-
|
|
179
|
-
|
|
221
|
+
if (ts < windowCutoff) return false;
|
|
222
|
+
if (excludeDevNoise && isDevSessionContractNoise(r)) return false;
|
|
223
|
+
return true;
|
|
224
|
+
})
|
|
225
|
+
.slice(-limit);
|
|
180
226
|
} catch { return []; }
|
|
181
227
|
}
|
|
182
228
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/db/migrate.mjs — Postgres migration runner.
|
|
3
|
+
*
|
|
4
|
+
* The runner only operates when a SQL client is explicitly configured. Migration
|
|
5
|
+
* files are repo-owned SQL, so sql.unsafe is limited to this static directory.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
|
|
12
|
+
const DB_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
export const MIGRATIONS_DIR = path.join(DB_DIR, 'migrations');
|
|
14
|
+
export const MIGRATIONS_TABLE = 'construct_schema_migrations';
|
|
15
|
+
|
|
16
|
+
function migrationId(filePath) {
|
|
17
|
+
return path.basename(filePath).replace(/\.sql$/i, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function listMigrationFiles({ migrationsDir = MIGRATIONS_DIR } = {}) {
|
|
21
|
+
if (!fs.existsSync(migrationsDir)) return [];
|
|
22
|
+
return fs.readdirSync(migrationsDir)
|
|
23
|
+
.filter((name) => /^\d+_.+\.sql$/i.test(name))
|
|
24
|
+
.sort()
|
|
25
|
+
.map((name) => path.join(migrationsDir, name));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function ensureMigrationsTable(sql) {
|
|
29
|
+
await sql`
|
|
30
|
+
CREATE TABLE IF NOT EXISTS construct_schema_migrations (
|
|
31
|
+
id TEXT PRIMARY KEY,
|
|
32
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
33
|
+
)
|
|
34
|
+
`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function getAppliedMigrations(sql) {
|
|
38
|
+
await ensureMigrationsTable(sql);
|
|
39
|
+
const rows = await sql`SELECT id, applied_at FROM construct_schema_migrations ORDER BY id`;
|
|
40
|
+
return rows.map((row) => ({ id: row.id, appliedAt: row.applied_at }));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getMigrationStatus(sql, opts = {}) {
|
|
44
|
+
const files = listMigrationFiles(opts);
|
|
45
|
+
const applied = new Set((await getAppliedMigrations(sql)).map((row) => row.id));
|
|
46
|
+
return files.map((file) => {
|
|
47
|
+
const id = migrationId(file);
|
|
48
|
+
return { id, file, applied: applied.has(id) };
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function applyMigrations(sql, opts = {}) {
|
|
53
|
+
await ensureMigrationsTable(sql);
|
|
54
|
+
const applied = new Set((await getAppliedMigrations(sql)).map((row) => row.id));
|
|
55
|
+
const appliedNow = [];
|
|
56
|
+
|
|
57
|
+
for (const file of listMigrationFiles(opts)) {
|
|
58
|
+
const id = migrationId(file);
|
|
59
|
+
if (applied.has(id)) continue;
|
|
60
|
+
const body = fs.readFileSync(file, 'utf8');
|
|
61
|
+
await sql.begin(async (tx) => {
|
|
62
|
+
await tx.unsafe(body);
|
|
63
|
+
await tx`INSERT INTO construct_schema_migrations (id) VALUES (${id})`;
|
|
64
|
+
});
|
|
65
|
+
appliedNow.push(id);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { applied: appliedNow, status: await getMigrationStatus(sql, opts) };
|
|
69
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS construct_queue_items (
|
|
2
|
+
project TEXT NOT NULL,
|
|
3
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
4
|
+
queue_name TEXT NOT NULL DEFAULT 'intake',
|
|
5
|
+
item_id TEXT NOT NULL,
|
|
6
|
+
status TEXT NOT NULL DEFAULT 'pending' CHECK (
|
|
7
|
+
status IN ('pending', 'claimed', 'processed', 'skipped', 'awaiting_approval', 'dead_letter')
|
|
8
|
+
),
|
|
9
|
+
payload JSONB NOT NULL,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
12
|
+
available_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
13
|
+
attempt INTEGER NOT NULL DEFAULT 0,
|
|
14
|
+
max_attempts INTEGER NOT NULL DEFAULT 3,
|
|
15
|
+
claimed_by TEXT,
|
|
16
|
+
lease_expires_at TIMESTAMPTZ,
|
|
17
|
+
processed_at TIMESTAMPTZ,
|
|
18
|
+
processed_by TEXT,
|
|
19
|
+
skipped_at TIMESTAMPTZ,
|
|
20
|
+
skipped_by TEXT,
|
|
21
|
+
terminal_reason TEXT,
|
|
22
|
+
PRIMARY KEY (project, tenant_id, queue_name, item_id)
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
CREATE INDEX IF NOT EXISTS construct_queue_items_claim_idx
|
|
26
|
+
ON construct_queue_items (project, tenant_id, queue_name, status, available_at, created_at);
|
|
27
|
+
|
|
28
|
+
CREATE INDEX IF NOT EXISTS construct_queue_items_lease_idx
|
|
29
|
+
ON construct_queue_items (project, tenant_id, queue_name, lease_expires_at)
|
|
30
|
+
WHERE status = 'claimed';
|
|
31
|
+
|
|
32
|
+
CREATE TABLE IF NOT EXISTS construct_queue_claims (
|
|
33
|
+
claim_id TEXT PRIMARY KEY,
|
|
34
|
+
project TEXT NOT NULL,
|
|
35
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
36
|
+
queue_name TEXT NOT NULL DEFAULT 'intake',
|
|
37
|
+
item_id TEXT NOT NULL,
|
|
38
|
+
worker_id TEXT NOT NULL,
|
|
39
|
+
attempt INTEGER NOT NULL,
|
|
40
|
+
claimed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
41
|
+
lease_expires_at TIMESTAMPTZ NOT NULL,
|
|
42
|
+
released_at TIMESTAMPTZ,
|
|
43
|
+
release_reason TEXT,
|
|
44
|
+
FOREIGN KEY (project, tenant_id, queue_name, item_id)
|
|
45
|
+
REFERENCES construct_queue_items (project, tenant_id, queue_name, item_id)
|
|
46
|
+
ON DELETE CASCADE
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE INDEX IF NOT EXISTS construct_queue_claims_item_idx
|
|
50
|
+
ON construct_queue_claims (project, tenant_id, queue_name, item_id, claimed_at DESC);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS construct_workers (
|
|
2
|
+
project TEXT NOT NULL,
|
|
3
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
4
|
+
worker_id TEXT NOT NULL,
|
|
5
|
+
host TEXT,
|
|
6
|
+
pid INTEGER,
|
|
7
|
+
capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
8
|
+
registered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
9
|
+
heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
10
|
+
lease_ttl_seconds INTEGER NOT NULL DEFAULT 120,
|
|
11
|
+
status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'stale', 'stopped')),
|
|
12
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
13
|
+
PRIMARY KEY (project, tenant_id, worker_id)
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX IF NOT EXISTS construct_workers_heartbeat_idx
|
|
17
|
+
ON construct_workers (project, tenant_id, heartbeat_at);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS construct_trace_events (
|
|
2
|
+
project TEXT NOT NULL,
|
|
3
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
4
|
+
trace_id TEXT NOT NULL,
|
|
5
|
+
span_id TEXT NOT NULL,
|
|
6
|
+
parent_span_id TEXT,
|
|
7
|
+
event_type TEXT NOT NULL,
|
|
8
|
+
role TEXT,
|
|
9
|
+
task_id TEXT,
|
|
10
|
+
metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
|
|
11
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
12
|
+
PRIMARY KEY (project, tenant_id, trace_id, span_id)
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE INDEX IF NOT EXISTS construct_trace_events_trace_idx
|
|
16
|
+
ON construct_trace_events (project, tenant_id, trace_id, created_at);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS construct_shared_memory (
|
|
2
|
+
id TEXT NOT NULL,
|
|
3
|
+
project TEXT NOT NULL,
|
|
4
|
+
tenant_id TEXT NOT NULL DEFAULT 'local',
|
|
5
|
+
category TEXT NOT NULL,
|
|
6
|
+
summary TEXT,
|
|
7
|
+
content TEXT,
|
|
8
|
+
tags JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9
|
+
provenance JSONB NOT NULL,
|
|
10
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
11
|
+
PRIMARY KEY (project, tenant_id, id)
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
CREATE INDEX IF NOT EXISTS construct_shared_memory_project_idx
|
|
15
|
+
ON construct_shared_memory (project, tenant_id, created_at);
|
|
@@ -14,11 +14,9 @@
|
|
|
14
14
|
"architect-to-data-engineer",
|
|
15
15
|
"architect-to-evaluator",
|
|
16
16
|
"architect-to-legal-compliance",
|
|
17
|
-
"construct-to-rd-lead",
|
|
18
17
|
"product-manager-to-architect",
|
|
19
18
|
"product-manager-to-data-analyst",
|
|
20
19
|
"product-manager-to-ux-researcher",
|
|
21
|
-
"qa-to-test-automation",
|
|
22
20
|
"researcher-to-product-manager",
|
|
23
21
|
"rule:common/comments",
|
|
24
22
|
"rule:common/no-fabrication",
|
|
@@ -165,8 +165,9 @@ function contractDecisions({ repoRoot, bindings }) {
|
|
|
165
165
|
const checks = [];
|
|
166
166
|
let advisoryPostconditions = 0;
|
|
167
167
|
for (const pc of c.postconditions || []) {
|
|
168
|
-
if (typeof pc
|
|
169
|
-
|
|
168
|
+
if (typeof pc !== 'object' || pc === null) { advisoryPostconditions += 1; continue; }
|
|
169
|
+
if (pc.check) checks.push(pc.id || pc.check);
|
|
170
|
+
else if (pc.postconditionType === 'advisory' || !pc.postconditionType) advisoryPostconditions += 1;
|
|
170
171
|
}
|
|
171
172
|
const enforcingTests = bindings.get(c.id) || [];
|
|
172
173
|
out.push({
|
|
@@ -25,7 +25,7 @@ export const PARITY_CONTRACT = {
|
|
|
25
25
|
queue: {
|
|
26
26
|
parityClass: 'parity',
|
|
27
27
|
capability: 'Work intake queue (enqueue, list pending, mark processed)',
|
|
28
|
-
rationale: '
|
|
28
|
+
rationale: 'One kind:queue contract via lib/intake/queue.mjs; filesystem is the solo default and Postgres is the team/enterprise default with explicit degraded git fallback.',
|
|
29
29
|
evidence: 'tests/intake-queue-factory.test.mjs',
|
|
30
30
|
},
|
|
31
31
|
memory: {
|
package/lib/deployment-mode.mjs
CHANGED
|
@@ -7,13 +7,20 @@
|
|
|
7
7
|
* (`getDeploymentMode(env, {cwd})`); the resource topology for each mode
|
|
8
8
|
* is derived here, not stored.
|
|
9
9
|
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
10
12
|
import { loadProjectConfig } from './config/project-config.mjs';
|
|
13
|
+
import { resolveTenantContext, TenantResolutionError } from './tenant/context.mjs';
|
|
11
14
|
|
|
12
15
|
export const DEPLOYMENT_MODES = ['solo', 'team', 'enterprise'];
|
|
13
16
|
export const DEFAULT_DEPLOYMENT_MODE = 'solo';
|
|
14
17
|
|
|
15
18
|
export const DEPLOYMENT_MODE_ENV_KEY = 'CONSTRUCT_DEPLOYMENT_MODE';
|
|
16
19
|
|
|
20
|
+
// The queue dimension names the default kind:'queue' provider, not a hardcoded
|
|
21
|
+
// backend. Solo keeps the zero-dependency local provider; team/enterprise now
|
|
22
|
+
// default to Postgres so distributed claims are row-locked and lease-backed.
|
|
23
|
+
|
|
17
24
|
const RESOURCE_TOPOLOGY = {
|
|
18
25
|
solo: {
|
|
19
26
|
queue: 'filesystem',
|
|
@@ -45,8 +52,8 @@ const RESOURCE_TOPOLOGY = {
|
|
|
45
52
|
};
|
|
46
53
|
|
|
47
54
|
const MODE_DESCRIPTIONS = {
|
|
48
|
-
solo: 'Individual usage. Filesystem queue, local repo state, optional Postgres/Docker/telemetry.',
|
|
49
|
-
team: 'Shared usage. Postgres queue, shared memory, Docker worker pool, central telemetry, brokered MCP.',
|
|
55
|
+
solo: 'Individual usage. Filesystem intake queue, local repo state, optional Postgres/Docker/telemetry.',
|
|
56
|
+
team: 'Shared usage. Postgres intake queue, shared memory, Docker worker pool, central telemetry, brokered MCP.',
|
|
50
57
|
enterprise: 'Hardened usage. Adds tenant isolation, RBAC/ABAC scaffolding, isolated worker containers, signed MCP allowlists, mandatory audit on top of team.',
|
|
51
58
|
};
|
|
52
59
|
|
|
@@ -80,7 +87,76 @@ export function resolveResourceMode(mode = DEFAULT_DEPLOYMENT_MODE) {
|
|
|
80
87
|
return { ...topology };
|
|
81
88
|
}
|
|
82
89
|
|
|
90
|
+
// ADR-0057 (A7) IMPLEMENT-NOW: enterprise mode without a resolvable tenant id
|
|
91
|
+
// fails closed at startup rather than running unlabeled multi-tenant traffic.
|
|
92
|
+
// Solo/team resolve to the explicit default tenant 'local' and never throw
|
|
93
|
+
// here. Call once per process/runtime start (CLI entry, MCP server boot,
|
|
94
|
+
// orchestration runtime) — resolveTenantContext itself is cheap and pure, so
|
|
95
|
+
// re-validating per call is safe but redundant beyond the first.
|
|
96
|
+
|
|
97
|
+
export function validateTenantAtStartup(env = process.env, { cwd } = {}) {
|
|
98
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
99
|
+
let config = null;
|
|
100
|
+
try {
|
|
101
|
+
({ config } = loadProjectConfig(cwd, env));
|
|
102
|
+
} catch { /* loader is best-effort — resolveTenantContext still sees env */ }
|
|
103
|
+
return resolveTenantContext({ env, config, mode });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export { TenantResolutionError };
|
|
107
|
+
|
|
83
108
|
export function describeResourceLine(mode = DEFAULT_DEPLOYMENT_MODE) {
|
|
84
109
|
const r = resolveResourceMode(mode);
|
|
85
110
|
return `queue:${r.queue} · memory:${r.memory} · workers:${r.workers} · telemetry:${r.telemetry}`;
|
|
86
111
|
}
|
|
112
|
+
|
|
113
|
+
// Thrown when a team/enterprise mode requires a capability that is unavailable
|
|
114
|
+
// and the caller has not opted into degraded operation via CONSTRUCT_DEGRADED_OK.
|
|
115
|
+
|
|
116
|
+
export class DeploymentModeError extends Error {
|
|
117
|
+
constructor(message) {
|
|
118
|
+
super(message);
|
|
119
|
+
this.name = 'DeploymentModeError';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* requireTeamCapabilityOrDegrade(subsystem, isAvailable, env, { cwd } = {})
|
|
125
|
+
*
|
|
126
|
+
* Enforces mode honesty: team/enterprise modes must not silently fall back to
|
|
127
|
+
* solo behavior when a required subsystem is unavailable.
|
|
128
|
+
*
|
|
129
|
+
* - If mode is solo: no-op (solo degrades gracefully by design).
|
|
130
|
+
* - If mode is team/enterprise AND isAvailable is true: no-op.
|
|
131
|
+
* - If mode is team/enterprise AND isAvailable is false:
|
|
132
|
+
* - If CONSTRUCT_DEGRADED_OK includes `subsystem` → logs a durable degradation
|
|
133
|
+
* record to `.cx/degradation.jsonl` (if cwd is available) and returns normally.
|
|
134
|
+
* - Otherwise → throws DeploymentModeError.
|
|
135
|
+
*/
|
|
136
|
+
export function requireTeamCapabilityOrDegrade(subsystem, isAvailable, env = process.env, { cwd } = {}) {
|
|
137
|
+
const mode = getDeploymentMode(env, { cwd });
|
|
138
|
+
if (mode === 'solo') return; // solo is always best-effort
|
|
139
|
+
if (isAvailable) return;
|
|
140
|
+
|
|
141
|
+
const degradedOk = String(env?.CONSTRUCT_DEGRADED_OK || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
142
|
+
if (!degradedOk.includes(subsystem)) {
|
|
143
|
+
throw new DeploymentModeError(
|
|
144
|
+
`team mode requires ${subsystem}; set CONSTRUCT_DEGRADED_OK=${subsystem} to allow degraded operation`,
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Degraded operation is explicitly permitted — write a durable record and continue.
|
|
149
|
+
const record = JSON.stringify({
|
|
150
|
+
ts: new Date().toISOString(),
|
|
151
|
+
mode,
|
|
152
|
+
subsystem,
|
|
153
|
+
degradedOk: true,
|
|
154
|
+
});
|
|
155
|
+
if (cwd) {
|
|
156
|
+
try {
|
|
157
|
+
const cxDir = path.join(cwd, '.cx');
|
|
158
|
+
fs.mkdirSync(cxDir, { recursive: true });
|
|
159
|
+
fs.appendFileSync(path.join(cxDir, 'degradation.jsonl'), record + '\n', 'utf8');
|
|
160
|
+
} catch { /* degradation log is best-effort */ }
|
|
161
|
+
}
|
|
162
|
+
}
|
package/lib/diagram-export.mjs
CHANGED
|
@@ -53,7 +53,16 @@ const MERMAID_INIT = `%%{init: {
|
|
|
53
53
|
'look': 'handDrawn',
|
|
54
54
|
'fontFamily': '${MERMAID_FONT}',
|
|
55
55
|
'flowchart': {
|
|
56
|
-
'htmlLabels': false
|
|
56
|
+
'htmlLabels': false,
|
|
57
|
+
'nodeSpacing': 46,
|
|
58
|
+
'rankSpacing': 56,
|
|
59
|
+
'padding': 12
|
|
60
|
+
},
|
|
61
|
+
'quadrantChart': {
|
|
62
|
+
'chartWidth': 620,
|
|
63
|
+
'chartHeight': 460,
|
|
64
|
+
'pointLabelFontSize': 13,
|
|
65
|
+
'pointRadius': 4
|
|
57
66
|
},
|
|
58
67
|
'themeVariables': {
|
|
59
68
|
'fontFamily': '${MERMAID_FONT}',
|
package/lib/distill.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
UTF8_TEXT_EXTS,
|
|
18
18
|
extractDocumentText as extractSharedDocumentText,
|
|
19
19
|
} from './document-extract.mjs';
|
|
20
|
+
import { isMainModule } from './roots.mjs';
|
|
20
21
|
|
|
21
22
|
const TEXT_EXTS = EXTRACTABLE_DOCUMENT_EXTS;
|
|
22
23
|
|
|
@@ -359,7 +360,9 @@ export async function distill(dir, { format = 'summary', query = '', mode = 'aut
|
|
|
359
360
|
const result = await withGenAiSpan('chat', {
|
|
360
361
|
[GenAiAttrs.SYSTEM]: 'anthropic',
|
|
361
362
|
[GenAiAttrs.OPERATION_NAME]: 'chat',
|
|
362
|
-
|
|
363
|
+
// Telemetry label only — the real model is whatever the user's claude CLI
|
|
364
|
+
// resolves; never imply a pinned model id here.
|
|
365
|
+
[GenAiAttrs.REQUEST_MODEL]: process.env.CONSTRUCT_MODEL || 'claude-cli-default',
|
|
363
366
|
'construct.operation': 'distill',
|
|
364
367
|
'construct.prompt_chars': prompt.length,
|
|
365
368
|
}, async (span) => {
|
|
@@ -412,7 +415,7 @@ export async function runDistillCli(argv = process.argv.slice(2)) {
|
|
|
412
415
|
});
|
|
413
416
|
}
|
|
414
417
|
|
|
415
|
-
const isMain = import.meta.url
|
|
418
|
+
const isMain = isMainModule(import.meta.url);
|
|
416
419
|
if (isMain) {
|
|
417
420
|
const { positional, flags } = parseArgs(process.argv.slice(2));
|
|
418
421
|
const dir = positional[0];
|
package/lib/docs-verify.mjs
CHANGED
|
@@ -23,6 +23,7 @@ import { statSync, readFileSync, existsSync } from "node:fs";
|
|
|
23
23
|
import { spawnSync } from "node:child_process";
|
|
24
24
|
import { hasIntakeReference } from "./intake/traceability.mjs";
|
|
25
25
|
import { ensureCxDir } from "./project-init-shared.mjs";
|
|
26
|
+
import { isMainModule } from './roots.mjs';
|
|
26
27
|
|
|
27
28
|
const args = process.argv.slice(2);
|
|
28
29
|
const quickMode = args.includes("--quick");
|
|
@@ -463,7 +464,7 @@ function main() {
|
|
|
463
464
|
process.exit(0);
|
|
464
465
|
}
|
|
465
466
|
|
|
466
|
-
if (import.meta.url
|
|
467
|
+
if (isMainModule(import.meta.url)) {
|
|
467
468
|
main();
|
|
468
469
|
}
|
|
469
470
|
|
package/lib/doctor/cli.mjs
CHANGED
|
@@ -85,6 +85,7 @@ export async function runCli(args) {
|
|
|
85
85
|
import('./watchers/service-health.mjs'),
|
|
86
86
|
import('./watchers/bd-watch.mjs'),
|
|
87
87
|
import('./watchers/consistency.mjs'),
|
|
88
|
+
import('./watchers/orchestration-runs.mjs'),
|
|
88
89
|
]);
|
|
89
90
|
for (const w of watchers) {
|
|
90
91
|
const r = await w.tick();
|