@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
package/lib/graph/staleness.mjs
CHANGED
|
@@ -1,44 +1,275 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* lib/graph/staleness.mjs —
|
|
2
|
+
* lib/graph/staleness.mjs — per-source seed-hash staleness checks (LMCP-C6).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* `hashFiles`/GRAPH_SEED_FILES (build-from-registry.mjs) hash a flat file list
|
|
5
|
+
* and cannot see inside a directory — the historical seed list has always
|
|
6
|
+
* included whole-directory entries (specialists/org, docs) which `readFileSync`
|
|
7
|
+
* silently treats as always-missing, so a change inside the directory never
|
|
8
|
+
* moved the hash. `hashSourceGroup` fixes this by walking directories
|
|
9
|
+
* recursively and hashing every file's relative path plus contents; a single
|
|
10
|
+
* flat file hashes the same way. `SOURCE_GROUPS` names each seed source
|
|
11
|
+
* (registry, overlays, specialists/org, plugin dirs, provider manifests,
|
|
12
|
+
* workflow manifests) as its own group so `checkGraphStaleness` can report
|
|
13
|
+
* exactly which source drifted, not just that something did. The legacy
|
|
14
|
+
* combined `sourceHash` (single hash over the flat GRAPH_SEED_FILES list) is
|
|
15
|
+
* preserved for existing consumers (lib/oracle/read-model.mjs) that compare
|
|
16
|
+
* against it directly.
|
|
17
|
+
*
|
|
18
|
+
* `checkExecutionStaleness` (LMCP-C9) is a second, independent staleness
|
|
19
|
+
* dimension: source-hash staleness above asks "did the seeds change since
|
|
20
|
+
* the graph was built"; execution staleness asks "has this workflow actually
|
|
21
|
+
* run recently". A workflow can be source-fresh and execution-stale (nothing
|
|
22
|
+
* changed, but no one has run it in months) or the reverse, so the two are
|
|
23
|
+
* reported side by side rather than merged into one boolean. A workflow with
|
|
24
|
+
* zero runtime-evidence ever (lib/graph/runtime-evidence.mjs) is flagged
|
|
25
|
+
* `neverExecuted: true` distinctly from `stale: true` — the former is "no
|
|
26
|
+
* evidence exists", the latter is "evidence exists but has aged past the
|
|
27
|
+
* threshold" — so a caller never has to infer one from the absence of data
|
|
28
|
+
* that could equally mean the other.
|
|
7
29
|
*/
|
|
8
30
|
|
|
31
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
32
|
+
import path from 'node:path';
|
|
33
|
+
import { createHash } from 'node:crypto';
|
|
34
|
+
|
|
9
35
|
import { hashFiles } from './build-from-registry.mjs';
|
|
10
36
|
import { loadGraph } from './store.mjs';
|
|
37
|
+
import { resolveManifestDirs } from '../extensions/loader.mjs';
|
|
38
|
+
import { resolveWorkflowManifestDirs } from '../workflows/loader.mjs';
|
|
39
|
+
import { computeLastExecutionByWorkflow } from './runtime-evidence.mjs';
|
|
40
|
+
import { listWorkflowDefs } from '../embedded-contract/workflow-defs.mjs';
|
|
11
41
|
|
|
12
42
|
export const GRAPH_SEED_FILES = [
|
|
13
43
|
'registry/capabilities.json',
|
|
14
44
|
'specialists/org',
|
|
15
45
|
'lib/embedded-contract/workflow-defs.mjs',
|
|
46
|
+
'lib/extensions/manifest-schema.mjs',
|
|
47
|
+
'lib/extensions/loader.mjs',
|
|
48
|
+
'lib/extensions/validate.mjs',
|
|
49
|
+
'lib/extensions/manifests',
|
|
50
|
+
'docs',
|
|
51
|
+
'lib/registry/assemble.mjs',
|
|
16
52
|
];
|
|
17
53
|
|
|
54
|
+
function walkFiles(absPath) {
|
|
55
|
+
let st;
|
|
56
|
+
try { st = statSync(absPath); } catch { return null; }
|
|
57
|
+
if (st.isFile()) return [absPath];
|
|
58
|
+
if (!st.isDirectory()) return null;
|
|
59
|
+
const out = [];
|
|
60
|
+
const stack = [absPath];
|
|
61
|
+
while (stack.length) {
|
|
62
|
+
const dir = stack.pop();
|
|
63
|
+
let entries;
|
|
64
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { continue; }
|
|
65
|
+
for (const entry of entries) {
|
|
66
|
+
const full = path.join(dir, entry.name);
|
|
67
|
+
if (entry.isDirectory()) stack.push(full);
|
|
68
|
+
else if (entry.isFile()) out.push(full);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out.sort();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Hash a named group of seed paths (files and/or directories), recursing into
|
|
76
|
+
* directories so an edit anywhere inside moves the hash. Missing paths hash
|
|
77
|
+
* as a stable sentinel rather than throwing, so an optional seed (e.g. a
|
|
78
|
+
* project override file that may not exist) does not abort hashing.
|
|
79
|
+
*
|
|
80
|
+
* @param {string} rootDir
|
|
81
|
+
* @param {string[]} rels — repo-relative file or directory paths.
|
|
82
|
+
* @returns {string} 16-hex-char digest.
|
|
83
|
+
*/
|
|
84
|
+
export function hashSourceGroup(rootDir, rels) {
|
|
85
|
+
const h = createHash('sha256');
|
|
86
|
+
for (const rel of rels) {
|
|
87
|
+
const abs = path.join(rootDir, rel);
|
|
88
|
+
const files = walkFiles(abs);
|
|
89
|
+
if (!files) { h.update(`${rel}\0missing`); continue; }
|
|
90
|
+
for (const filePath of files) {
|
|
91
|
+
const relToRoot = path.relative(rootDir, filePath);
|
|
92
|
+
h.update(relToRoot);
|
|
93
|
+
try { h.update(readFileSync(filePath)); } catch { h.update('\0unreadable'); }
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return h.digest('hex').slice(0, 16);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Named seed-source groups making up the full graph seed set: registry
|
|
101
|
+
* catalog + overlays, specialists/org, plugin (pack) directories, provider
|
|
102
|
+
* manifests (builtin + user + project), and workflow manifests (builtin +
|
|
103
|
+
* project; pack tiers are resolved per-pack root so are not enumerable here).
|
|
104
|
+
*
|
|
105
|
+
* @param {string} rootDir
|
|
106
|
+
* @returns {Record<string,string[]>}
|
|
107
|
+
*/
|
|
108
|
+
export function sourceGroups(rootDir) {
|
|
109
|
+
const manifestDirs = resolveManifestDirs({ rootDir });
|
|
110
|
+
const workflowDirs = resolveWorkflowManifestDirs({ rootDir });
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
registry: ['registry/capabilities.json', 'lib/registry/assemble.mjs', 'lib/registry/loader.mjs'],
|
|
114
|
+
overlays: [
|
|
115
|
+
path.join('.cx', 'org'),
|
|
116
|
+
path.join('.cx', 'unified-registry.json'),
|
|
117
|
+
],
|
|
118
|
+
specialistsOrg: ['specialists/org'],
|
|
119
|
+
plugins: [path.join('.cx', 'packs'), 'lib/packs/manifests'],
|
|
120
|
+
providerManifests: [
|
|
121
|
+
manifestDirs.builtin,
|
|
122
|
+
manifestDirs.user,
|
|
123
|
+
manifestDirs.project,
|
|
124
|
+
path.join('.cx', 'providers.json'),
|
|
125
|
+
].map((p) => path.isAbsolute(p) ? path.relative(rootDir, p) : p),
|
|
126
|
+
workflowManifests: [
|
|
127
|
+
workflowDirs.builtin,
|
|
128
|
+
workflowDirs.project,
|
|
129
|
+
'lib/embedded-contract/workflow-defs.mjs',
|
|
130
|
+
].map((p) => path.isAbsolute(p) ? path.relative(rootDir, p) : p),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @param {string} rootDir
|
|
136
|
+
* @returns {Record<string,string>} hash per named source group.
|
|
137
|
+
*/
|
|
138
|
+
export function computeSourceHashes(rootDir) {
|
|
139
|
+
const groups = sourceGroups(rootDir);
|
|
140
|
+
const out = {};
|
|
141
|
+
for (const [name, rels] of Object.entries(groups)) {
|
|
142
|
+
out[name] = hashSourceGroup(rootDir, rels);
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
18
147
|
/**
|
|
19
148
|
* @param {string} rootDir — project root holding .cx/graph/.
|
|
20
|
-
* @returns {{
|
|
149
|
+
* @returns {{
|
|
150
|
+
* present: boolean,
|
|
151
|
+
* stale: boolean,
|
|
152
|
+
* staleReason: string|null,
|
|
153
|
+
* staleSources: string[],
|
|
154
|
+
* currentHash?: string,
|
|
155
|
+
* storedHash?: string|null,
|
|
156
|
+
* }}
|
|
21
157
|
*/
|
|
22
158
|
export function checkGraphStaleness(rootDir) {
|
|
23
159
|
const graph = loadGraph(rootDir);
|
|
24
160
|
if (!graph.exists) {
|
|
25
|
-
return { present: false, stale: false, staleReason: null };
|
|
161
|
+
return { present: false, stale: false, staleReason: null, staleSources: [] };
|
|
26
162
|
}
|
|
27
163
|
|
|
28
164
|
try {
|
|
29
165
|
const current = hashFiles(rootDir, GRAPH_SEED_FILES);
|
|
30
166
|
const stored = graph.meta?.sourceHash ?? null;
|
|
31
|
-
|
|
167
|
+
|
|
168
|
+
const currentSources = computeSourceHashes(rootDir);
|
|
169
|
+
const storedSources = graph.meta?.sourceHashes ?? null;
|
|
170
|
+
const staleSources = [];
|
|
171
|
+
if (storedSources) {
|
|
172
|
+
for (const name of Object.keys(currentSources)) {
|
|
173
|
+
if (currentSources[name] !== storedSources[name]) staleSources.push(name);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const stale = staleSources.length > 0 || (stored != null && current !== stored && !storedSources);
|
|
178
|
+
if (stale) {
|
|
179
|
+
const reason = staleSources.length > 0
|
|
180
|
+
? `source(s) changed since last build: ${staleSources.join(', ')}`
|
|
181
|
+
: 'registry/contracts/workflow seeds changed since last build';
|
|
32
182
|
return {
|
|
33
183
|
present: true,
|
|
34
184
|
stale: true,
|
|
35
|
-
staleReason:
|
|
185
|
+
staleReason: reason,
|
|
186
|
+
staleSources,
|
|
36
187
|
currentHash: current,
|
|
37
188
|
storedHash: stored,
|
|
38
189
|
};
|
|
39
190
|
}
|
|
40
|
-
return { present: true, stale: false, staleReason: null, currentHash: current, storedHash: stored };
|
|
191
|
+
return { present: true, stale: false, staleReason: null, staleSources: [], currentHash: current, storedHash: stored };
|
|
41
192
|
} catch {
|
|
42
|
-
return { present: true, stale: false, staleReason: null };
|
|
193
|
+
return { present: true, stale: false, staleReason: null, staleSources: [] };
|
|
43
194
|
}
|
|
44
195
|
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Default execution-staleness threshold: a workflow with runtime evidence
|
|
199
|
+
* older than this many days is flagged stale. Overridable per-workflow via
|
|
200
|
+
* the `thresholds` option (keyed by workflow type), so a high-churn workflow
|
|
201
|
+
* can demand fresher evidence than a rarely-invoked one.
|
|
202
|
+
*/
|
|
203
|
+
export const EXECUTION_STALENESS_DEFAULT_THRESHOLD_DAYS = 30;
|
|
204
|
+
|
|
205
|
+
function ageDays(timestamp, now) {
|
|
206
|
+
const then = Date.parse(timestamp);
|
|
207
|
+
if (!Number.isFinite(then)) return null;
|
|
208
|
+
return (now - then) / (1000 * 60 * 60 * 24);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Per-workflow execution-staleness (LMCP-C9): how long since a workflow's
|
|
213
|
+
* last runtime-evidence edge (lib/graph/runtime-evidence.mjs), against a
|
|
214
|
+
* per-workflow threshold. A workflow with no evidence at all is reported
|
|
215
|
+
* `neverExecuted: true` and `stale: false` — distinct from a workflow whose
|
|
216
|
+
* evidence exists but has aged past the threshold (`stale: true`,
|
|
217
|
+
* `neverExecuted: false`) — so the two conditions are never conflated.
|
|
218
|
+
*
|
|
219
|
+
* @param {string} rootDir — project root; runs are read from its machine-scoped
|
|
220
|
+
* state root (ADR-0066), not a project-relative path.
|
|
221
|
+
* @param {{ thresholds?: Record<string,number>, now?: number }} [opts]
|
|
222
|
+
* @returns {{
|
|
223
|
+
* workflows: Record<string, {
|
|
224
|
+
* lastExecution: { timestamp: string, outcome: string, runId: string }|null,
|
|
225
|
+
* neverExecuted: boolean,
|
|
226
|
+
* stale: boolean,
|
|
227
|
+
* ageDays: number|null,
|
|
228
|
+
* thresholdDays: number,
|
|
229
|
+
* }>,
|
|
230
|
+
* neverExecuted: string[],
|
|
231
|
+
* staleWorkflows: string[],
|
|
232
|
+
* }}
|
|
233
|
+
*/
|
|
234
|
+
export function checkExecutionStaleness(rootDir, { thresholds = {}, now = Date.now() } = {}) {
|
|
235
|
+
const lastExecutionByWorkflow = computeLastExecutionByWorkflow(rootDir);
|
|
236
|
+
|
|
237
|
+
const workflows = {};
|
|
238
|
+
const neverExecuted = [];
|
|
239
|
+
const staleWorkflows = [];
|
|
240
|
+
|
|
241
|
+
for (const wf of listWorkflowDefs()) {
|
|
242
|
+
const lastExecution = lastExecutionByWorkflow[wf.type] ?? null;
|
|
243
|
+
const thresholdDays = thresholds[wf.type] ?? EXECUTION_STALENESS_DEFAULT_THRESHOLD_DAYS;
|
|
244
|
+
|
|
245
|
+
if (!lastExecution) {
|
|
246
|
+
neverExecuted.push(wf.type);
|
|
247
|
+
workflows[wf.type] = {
|
|
248
|
+
lastExecution: null,
|
|
249
|
+
neverExecuted: true,
|
|
250
|
+
stale: false,
|
|
251
|
+
ageDays: null,
|
|
252
|
+
thresholdDays,
|
|
253
|
+
};
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const age = ageDays(lastExecution.timestamp, now);
|
|
258
|
+
const stale = age !== null && age > thresholdDays;
|
|
259
|
+
if (stale) staleWorkflows.push(wf.type);
|
|
260
|
+
|
|
261
|
+
workflows[wf.type] = {
|
|
262
|
+
lastExecution,
|
|
263
|
+
neverExecuted: false,
|
|
264
|
+
stale,
|
|
265
|
+
ageDays: age,
|
|
266
|
+
thresholdDays,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
workflows,
|
|
272
|
+
neverExecuted: neverExecuted.sort(),
|
|
273
|
+
staleWorkflows: staleWorkflows.sort(),
|
|
274
|
+
};
|
|
275
|
+
}
|
package/lib/graph/store.mjs
CHANGED
|
@@ -3,15 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* The substrate for Construct's living dependency matrix. Nodes are typed
|
|
5
5
|
* entities (file, module, workflow, capability, test, contract, surface,
|
|
6
|
-
* skill, rule
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* skill, rule, provider, tool, pack, doc, specialist, runtime-evidence) addressed
|
|
7
|
+
* by canonical ids of the form `type:key`. Edges are directed and carry a
|
|
8
|
+
* relation kind (imports, realizes, validates, covers, exposes, governed_by,
|
|
9
|
+
* uses, embeds, co_changes, contains, requires, documents, evidenced_by,
|
|
10
|
+
* owned_by) and a provenance source
|
|
11
|
+
* (registry, import-graph, co-change, override, corpus-annotation, runtime-evidence).
|
|
10
12
|
*
|
|
11
13
|
* Persisted under `.cx/graph/` as deterministic JSONL so a graph round-trips
|
|
12
14
|
* losslessly and diffs cleanly: nodes.jsonl (sorted by id), edges.jsonl
|
|
13
15
|
* (sorted by from,rel,to), meta.json (generatedAt, sourceHash, counts).
|
|
14
16
|
* Dependency-free and pure-JS per ADR-0001.
|
|
17
|
+
*
|
|
18
|
+
* `reads` (LMCP-E4): specialist --reads--> provider, seeded from pack
|
|
19
|
+
* `embedBindings` grants (build-from-registry.mjs). Makes the embed
|
|
20
|
+
* authority-guard's per-specialist read/search grant visible in
|
|
21
|
+
* `construct graph`/`construct matrix` rather than living only in pack JSON.
|
|
15
22
|
*/
|
|
16
23
|
|
|
17
24
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
|
|
@@ -19,13 +26,15 @@ import path from 'node:path';
|
|
|
19
26
|
|
|
20
27
|
export const NODE_TYPES = new Set([
|
|
21
28
|
'file', 'module', 'workflow', 'capability', 'test', 'contract', 'surface', 'skill', 'rule',
|
|
29
|
+
'provider', 'tool', 'pack', 'doc', 'specialist', 'runtime-evidence', 'embed',
|
|
22
30
|
]);
|
|
23
31
|
|
|
24
32
|
export const EDGE_RELS = new Set([
|
|
25
33
|
'imports', 'realizes', 'validates', 'covers', 'exposes', 'governed_by', 'uses', 'embeds', 'co_changes', 'contains',
|
|
34
|
+
'requires', 'documents', 'evidenced_by', 'owned_by', 'reads', 'secures',
|
|
26
35
|
]);
|
|
27
36
|
|
|
28
|
-
export const EDGE_SOURCES = new Set(['registry', 'import-graph', 'co-change', 'override']);
|
|
37
|
+
export const EDGE_SOURCES = new Set(['registry', 'import-graph', 'co-change', 'override', 'corpus-annotation', 'runtime-evidence', 'embed-manifest']);
|
|
29
38
|
|
|
30
39
|
const STORE_SUBDIR = path.join('.cx', 'graph');
|
|
31
40
|
|
|
@@ -98,11 +107,17 @@ function countBy(items, key) {
|
|
|
98
107
|
* Persist a full graph (regenerate semantics). Nodes and edges are normalized
|
|
99
108
|
* and sorted; meta records counts and the caller-supplied source hash.
|
|
100
109
|
*
|
|
110
|
+
* `sourceHashes` (LMCP-C6) is an optional `{ [sourceName]: hash }` map recording
|
|
111
|
+
* a hash per seed source (registry, overlays, specialists/org, plugins,
|
|
112
|
+
* provider manifests, workflow manifests) alongside the single combined
|
|
113
|
+
* `sourceHash`, so staleness detection can name which source drifted instead
|
|
114
|
+
* of only reporting "something changed".
|
|
115
|
+
*
|
|
101
116
|
* @param {string} rootDir
|
|
102
|
-
* @param {{ nodes: object[], edges: object[], generatedAt?: string, sourceHash?: string }} graph
|
|
117
|
+
* @param {{ nodes: object[], edges: object[], generatedAt?: string, sourceHash?: string, sourceHashes?: Record<string,string> }} graph
|
|
103
118
|
* @returns {{ nodeCount: number, edgeCount: number, dir: string }}
|
|
104
119
|
*/
|
|
105
|
-
export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null, sourceHash = null } = {}) {
|
|
120
|
+
export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null, sourceHash = null, sourceHashes = null } = {}) {
|
|
106
121
|
const dir = graphDir(rootDir);
|
|
107
122
|
mkdirSync(dir, { recursive: true });
|
|
108
123
|
const normNodes = normalizeNodes(nodes);
|
|
@@ -110,8 +125,10 @@ export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null
|
|
|
110
125
|
writeAtomic(nodesPath(rootDir), normNodes.map((n) => JSON.stringify(n)).join('\n') + (normNodes.length ? '\n' : ''));
|
|
111
126
|
writeAtomic(edgesPath(rootDir), normEdges.map((e) => JSON.stringify(e)).join('\n') + (normEdges.length ? '\n' : ''));
|
|
112
127
|
const meta = {
|
|
128
|
+
schemaVersion: 1,
|
|
113
129
|
generatedAt,
|
|
114
130
|
sourceHash,
|
|
131
|
+
sourceHashes: sourceHashes || null,
|
|
115
132
|
nodeCount: normNodes.length,
|
|
116
133
|
edgeCount: normEdges.length,
|
|
117
134
|
nodesByType: countBy(normNodes, 'type'),
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/validate.mjs — living-graph structural validation.
|
|
3
|
+
*
|
|
4
|
+
* validateGraph() checks workflow→provider→tool requires-edges, provider-manifest
|
|
5
|
+
* disk presence, doc-file existence, capability test coverage (LMCP-C5,
|
|
6
|
+
* sourced from lib/graph/gaps.mjs so `graph validate` and
|
|
7
|
+
* `graph missing-tests` never disagree), workflow-manifest liveness
|
|
8
|
+
* (roleChain resolution, skill existence, acyclic handoffs, surface/mode
|
|
9
|
+
* reachability — LMCP-C11, lib/workflows/liveness.mjs), and workflow
|
|
10
|
+
* surface parity (declared `surfaces` vs actual CLI/MCP/SDK registration —
|
|
11
|
+
* LMCP-D4, lib/workflows/surface-parity.mjs), classifying each gap
|
|
12
|
+
* as error vs warning per deployment mode (solo lenient, team/enterprise
|
|
13
|
+
* strict). Backs `construct graph validate`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { loadGraph, nodesByType, dependenciesOf, dependentsOf } from './store.mjs';
|
|
17
|
+
import { getDeploymentMode } from '../deployment-mode.mjs';
|
|
18
|
+
import { loadAllWorkflows } from '../workflows/loader.mjs';
|
|
19
|
+
import { checkWorkflowLiveness } from '../workflows/liveness.mjs';
|
|
20
|
+
import { checkSurfaceParity } from '../workflows/surface-parity.mjs';
|
|
21
|
+
import { findMissingTestCapabilities } from './gaps.mjs';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import path from 'node:path';
|
|
24
|
+
|
|
25
|
+
const MANIFESTS_REL_DIR = path.join('lib', 'extensions', 'manifests');
|
|
26
|
+
|
|
27
|
+
export function nodeParts(id) {
|
|
28
|
+
const colon = id.indexOf(':');
|
|
29
|
+
return colon === -1 ? { type: null, key: id } : { type: id.slice(0, colon), key: id.slice(colon + 1) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function validateGraph(rootDir, { strict = false, deploymentMode } = {}) {
|
|
33
|
+
const graph = loadGraph(rootDir);
|
|
34
|
+
if (!graph.exists) {
|
|
35
|
+
return { valid: false, errors: ['no graph found — run construct graph build first'], warnings: [], infos: [] };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const mode = deploymentMode || getDeploymentMode(process.env, { cwd: rootDir });
|
|
39
|
+
const isStrict = mode === 'team' || mode === 'enterprise' || strict;
|
|
40
|
+
|
|
41
|
+
const errors = [];
|
|
42
|
+
const warnings = [];
|
|
43
|
+
const infos = [];
|
|
44
|
+
|
|
45
|
+
const workflowNodes = nodesByType(graph, 'workflow');
|
|
46
|
+
for (const wf of workflowNodes) {
|
|
47
|
+
const embeddingCaps = dependentsOf(graph, wf.id, 'embeds');
|
|
48
|
+
for (const capId of embeddingCaps) {
|
|
49
|
+
const usedToolIds = dependenciesOf(graph, capId, 'uses')
|
|
50
|
+
.filter(id => id.startsWith('provider:'));
|
|
51
|
+
for (const providerId of usedToolIds) {
|
|
52
|
+
const providerToolIds = dependenciesOf(graph, providerId, 'requires');
|
|
53
|
+
const hasRequiredTools = providerToolIds.some(toolId =>
|
|
54
|
+
dependenciesOf(graph, capId, 'requires').includes(toolId)
|
|
55
|
+
);
|
|
56
|
+
if (!hasRequiredTools) {
|
|
57
|
+
const providerName = nodeParts(providerId).key;
|
|
58
|
+
const msg = `workflow '${wf.name || wf.id}' embeds capability '${capId}' missing 'requires' edge to provider tools for '${providerName}'`;
|
|
59
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const providerNodes = nodesByType(graph, 'provider');
|
|
66
|
+
for (const provider of providerNodes) {
|
|
67
|
+
const providerName = provider.attrs?.id || nodeParts(provider.id).key;
|
|
68
|
+
const manifestPath = path.join(rootDir, MANIFESTS_REL_DIR, `${providerName}.manifest.json`);
|
|
69
|
+
if (!existsSync(manifestPath)) {
|
|
70
|
+
const msg = `provider '${provider.id}' has no manifest on disk at ${path.relative(rootDir, manifestPath)}`;
|
|
71
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const docNodes = nodesByType(graph, 'doc');
|
|
76
|
+
for (const doc of docNodes) {
|
|
77
|
+
const docRel = doc.attrs?.path || nodeParts(doc.id).key;
|
|
78
|
+
if (docRel) {
|
|
79
|
+
const resolved = path.isAbsolute(docRel) ? docRel : path.join(rootDir, docRel);
|
|
80
|
+
if (!existsSync(resolved)) {
|
|
81
|
+
const msg = `doc '${doc.id}' references file '${resolved}' that does not exist`;
|
|
82
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Capability test coverage (LMCP-C5): sourced from the same
|
|
88
|
+
// findMissingTestCapabilities the `missing-tests` gap query uses, so the
|
|
89
|
+
// two never drift out of consistency with each other.
|
|
90
|
+
const gaps = findMissingTestCapabilities(rootDir);
|
|
91
|
+
for (const capId of gaps.capabilities) {
|
|
92
|
+
const msg = `capability '${capId}' has zero validating tests`;
|
|
93
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
94
|
+
}
|
|
95
|
+
for (const wfId of gaps.workflows) {
|
|
96
|
+
const msg = `workflow '${wfId}' has zero validated embedding capability`;
|
|
97
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Embed-node integrity (LMCP-P6): every binding an embed manifest declares
|
|
101
|
+
// must resolve to a real node. A `uses`/`owned_by`/`governed_by` edge whose
|
|
102
|
+
// target node is absent is a broken binding — the manifest names a provider,
|
|
103
|
+
// specialist, or contract that does not exist in the graph — and is drift the
|
|
104
|
+
// C8 gate must catch. An embed with zero validating tests is treated like an
|
|
105
|
+
// untested capability so presets cannot silently lose coverage.
|
|
106
|
+
const embedTargetRels = [
|
|
107
|
+
['uses', 'provider'],
|
|
108
|
+
['owned_by', 'specialist'],
|
|
109
|
+
['governed_by', 'contract'],
|
|
110
|
+
];
|
|
111
|
+
for (const embed of nodesByType(graph, 'embed')) {
|
|
112
|
+
for (const [rel, kind] of embedTargetRels) {
|
|
113
|
+
for (const targetId of dependenciesOf(graph, embed.id, rel)) {
|
|
114
|
+
if (!graph.nodes.has(targetId)) {
|
|
115
|
+
const msg = `embed '${embed.name || embed.id}' binds ${kind} '${targetId}' which has no node in the graph`;
|
|
116
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (dependentsOf(graph, embed.id, 'validates').length === 0) {
|
|
121
|
+
const msg = `embed '${embed.name || embed.id}' has zero validating tests`;
|
|
122
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Security-coverage edge integrity (LMCP-N8): a `@secures <id>` tag that
|
|
127
|
+
// names a workflow/embed with no node is a typo'd or stale link that would
|
|
128
|
+
// silently contribute zero coverage — flag the dangling target.
|
|
129
|
+
for (const edge of graph.edges) {
|
|
130
|
+
if (edge.rel !== 'secures') continue;
|
|
131
|
+
if (!graph.nodes.has(edge.to)) {
|
|
132
|
+
const msg = `security test '${edge.from}' @secures '${edge.to}' which has no node in the graph`;
|
|
133
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Workflow liveness (LMCP-C11): schema validation only checks shape; these
|
|
138
|
+
// checks catch a roleChain naming a nonexistent specialist, a circular
|
|
139
|
+
// handoff, or a workflow no surface/mode can reach — failures that
|
|
140
|
+
// otherwise surface only at workflow-run time.
|
|
141
|
+
const { workflows } = loadAllWorkflows({ rootDir });
|
|
142
|
+
const { violations } = checkWorkflowLiveness(workflows, { rootDir });
|
|
143
|
+
for (const msg of violations) {
|
|
144
|
+
if (isStrict) errors.push(msg); else warnings.push(msg);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Workflow surface parity (LMCP-D4): a manifest's declared `surfaces` must
|
|
148
|
+
// match its actual CLI/MCP registration (both wrap the same invokeWorkflow
|
|
149
|
+
// core, so any undeclared divergence is a real authoring drift, not a
|
|
150
|
+
// runtime possibility) — an undeclared mismatch is always an error,
|
|
151
|
+
// regardless of deployment mode, since it is never a matter of team policy.
|
|
152
|
+
const { errors: surfaceErrors, infos: surfaceInfos } = checkSurfaceParity(workflows);
|
|
153
|
+
errors.push(...surfaceErrors);
|
|
154
|
+
infos.push(...surfaceInfos);
|
|
155
|
+
|
|
156
|
+
infos.push('degraded_by check: not yet implemented (no degraded_by edges in C1 scope)');
|
|
157
|
+
infos.push('evidenced_by edges: sourced from persisted orchestration runs (LMCP-C9, see lib/graph/runtime-evidence.mjs and `graph explain`)');
|
|
158
|
+
|
|
159
|
+
const valid = errors.length === 0;
|
|
160
|
+
return { valid, errors, warnings, infos };
|
|
161
|
+
}
|
package/lib/headhunt.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { selectOption } from './tty-prompts.mjs';
|
|
|
16
16
|
import { resolveActiveScope } from './scopes/loader.mjs';
|
|
17
17
|
import { classifyObjectiveForScope } from './scopes/teams.mjs';
|
|
18
18
|
import { loadRegistry } from './registry/loader.mjs';
|
|
19
|
+
import { isMainModule } from './roots.mjs';
|
|
19
20
|
|
|
20
21
|
const OVERLAY_DIRNAME = 'domain-overlays';
|
|
21
22
|
const PROMOTION_DIRNAME = 'promotion-requests';
|
|
@@ -102,7 +103,7 @@ export function classifyOverlay(objective = '', explicitTeam = '', { cwd = proce
|
|
|
102
103
|
if (/runbook|docs|documentation|guide|onboard/.test(text)) {
|
|
103
104
|
return {
|
|
104
105
|
focus: 'documentation',
|
|
105
|
-
attachTo: ['cx-
|
|
106
|
+
attachTo: ['cx-operations', 'cx-researcher'],
|
|
106
107
|
teamFocus: 'operations-group',
|
|
107
108
|
recommendedTeam: 'operations-group',
|
|
108
109
|
};
|
|
@@ -292,10 +293,10 @@ export function promoteHeadhunt(id, { cwd = process.cwd(), owner = null } = {})
|
|
|
292
293
|
attachTo: overlayEntry.record.attachTo,
|
|
293
294
|
focus: overlayEntry.record.focus,
|
|
294
295
|
sourceOverlay: overlayEntry.filePath,
|
|
295
|
-
reviewFlow: ['cx-architect', 'cx-
|
|
296
|
+
reviewFlow: ['cx-architect', 'cx-reviewer', 'cx-operations'],
|
|
296
297
|
challenge: {
|
|
297
298
|
required: true,
|
|
298
|
-
owner: 'cx-
|
|
299
|
+
owner: 'cx-reviewer',
|
|
299
300
|
status: 'pending',
|
|
300
301
|
reason: 'Persistent capabilities must be challenged before promotion.',
|
|
301
302
|
},
|
|
@@ -313,7 +314,7 @@ export function updatePromotionChallenge(id, { cwd = process.cwd(), status, note
|
|
|
313
314
|
const requestPath = path.join(getPromotionDir(root), `${id}.json`);
|
|
314
315
|
const request = readJsonIfExists(requestPath);
|
|
315
316
|
if (!request) throw new Error(`Unknown promotion request: ${id}`);
|
|
316
|
-
request.challenge = request.challenge || { required: true, owner: 'cx-
|
|
317
|
+
request.challenge = request.challenge || { required: true, owner: 'cx-reviewer', status: 'pending' };
|
|
317
318
|
if (status) request.challenge.status = status;
|
|
318
319
|
if (note) request.challenge.note = note;
|
|
319
320
|
request.updatedAt = new Date().toISOString();
|
|
@@ -539,7 +540,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
|
|
|
539
540
|
promotionGates: buildPromotionGates(),
|
|
540
541
|
challenge: permanence === 'temporary' ? null : {
|
|
541
542
|
required: true,
|
|
542
|
-
owner: 'cx-
|
|
543
|
+
owner: 'cx-reviewer',
|
|
543
544
|
status: 'pending',
|
|
544
545
|
reason: 'Promotion requests must be challenged before any persistent capability is added.',
|
|
545
546
|
},
|
|
@@ -587,10 +588,10 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
|
|
|
587
588
|
attachTo: classification.attachTo,
|
|
588
589
|
focus: classification.focus,
|
|
589
590
|
sourceOverlay: overlayJsonPath,
|
|
590
|
-
reviewFlow: ['cx-architect', 'cx-
|
|
591
|
+
reviewFlow: ['cx-architect', 'cx-reviewer', 'cx-operations'],
|
|
591
592
|
challenge: {
|
|
592
593
|
required: true,
|
|
593
|
-
owner: 'cx-
|
|
594
|
+
owner: 'cx-reviewer',
|
|
594
595
|
status: 'pending',
|
|
595
596
|
reason: 'Promotion requests must be challenged before any persistent capability is added.',
|
|
596
597
|
},
|
|
@@ -617,7 +618,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
|
|
|
617
618
|
}
|
|
618
619
|
}
|
|
619
620
|
|
|
620
|
-
if (import.meta.url
|
|
621
|
+
if (isMainModule(import.meta.url)) {
|
|
621
622
|
runHeadhunt({ args: process.argv.slice(2) }).catch((error) => {
|
|
622
623
|
process.stderr.write(`Error: ${error.message}\n`);
|
|
623
624
|
process.exit(1);
|
package/lib/health-check.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import path from 'node:path';
|
|
|
12
12
|
import { existsSync, readFileSync, chmodSync } from 'node:fs';
|
|
13
13
|
import { extractOpRef, hasAnySecret, hasSecret } from './providers/secret-resolver.mjs';
|
|
14
14
|
import { configDir } from './config/xdg.mjs';
|
|
15
|
+
import { getDeploymentMode } from './deployment-mode.mjs';
|
|
15
16
|
|
|
16
17
|
const isWindows = os.platform() === 'win32';
|
|
17
18
|
|
|
@@ -61,16 +62,22 @@ export async function checkPrerequisites(options = {}) {
|
|
|
61
62
|
const missing = [];
|
|
62
63
|
const warnings = [];
|
|
63
64
|
|
|
64
|
-
//
|
|
65
|
+
// Docker is required only when the deployment mode uses Docker worker pools (team/enterprise).
|
|
66
|
+
// Solo mode uses embedded LanceDB + Git-backed state; Postgres was removed, so Docker is optional.
|
|
67
|
+
const deploymentMode = getDeploymentMode(process.env);
|
|
68
|
+
const dockerRequired = deploymentMode === 'team' || deploymentMode === 'enterprise';
|
|
69
|
+
|
|
65
70
|
const dockerVersion = getVersion('docker');
|
|
66
71
|
const hasDocker = !!dockerVersion;
|
|
67
72
|
checks.push({
|
|
68
73
|
name: 'Docker',
|
|
69
|
-
required:
|
|
74
|
+
required: dockerRequired,
|
|
70
75
|
installed: hasDocker,
|
|
71
76
|
version: dockerVersion,
|
|
72
|
-
why:
|
|
73
|
-
|
|
77
|
+
why: dockerRequired
|
|
78
|
+
? 'Required for Docker worker pool in team/enterprise mode'
|
|
79
|
+
: 'Optional in solo mode (required for team/enterprise)',
|
|
80
|
+
install: isWindows
|
|
74
81
|
? 'https://docs.docker.com/desktop/install/windows-install/'
|
|
75
82
|
: 'https://docs.docker.com/get-docker/',
|
|
76
83
|
});
|
|
@@ -403,9 +410,10 @@ export async function quickHealthCheck(options = {}) {
|
|
|
403
410
|
const { homeDir, showCredentials = false } = options;
|
|
404
411
|
|
|
405
412
|
const critical = [];
|
|
406
|
-
|
|
407
|
-
//
|
|
408
|
-
|
|
413
|
+
|
|
414
|
+
// Docker is only critical in team/enterprise mode where Docker worker pools are required.
|
|
415
|
+
const deployMode = getDeploymentMode(process.env);
|
|
416
|
+
if ((deployMode === 'team' || deployMode === 'enterprise') && !commandExists('docker')) {
|
|
409
417
|
critical.push('Docker');
|
|
410
418
|
}
|
|
411
419
|
|
package/lib/hook-health.mjs
CHANGED
|
@@ -436,7 +436,7 @@ export function formatHealthStatus(health) {
|
|
|
436
436
|
output += `\nCRITICAL ISSUES:\n`;
|
|
437
437
|
output += `The following hooks are critical and not functioning:\n`;
|
|
438
438
|
output += health.criticalFailed.join(', ') + '\n';
|
|
439
|
-
output += `\nRecommendation: Run \`construct
|
|
439
|
+
output += `\nRecommendation: Run \`construct hooks:health --fix\` to attempt repair\n`;
|
|
440
440
|
}
|
|
441
441
|
|
|
442
442
|
return output;
|