@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/cli.mjs
CHANGED
|
@@ -2,11 +2,32 @@
|
|
|
2
2
|
* lib/graph/cli.mjs — `construct matrix` command surface.
|
|
3
3
|
*
|
|
4
4
|
* Subcommands:
|
|
5
|
-
* build
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* stat
|
|
9
|
-
* query <id>
|
|
5
|
+
* build Regenerate the dependency graph from the registry,
|
|
6
|
+
* corpus-annotation, embed-manifest (LMCP-P6), and static
|
|
7
|
+
* import-graph seeds into <projectDir>/.cx/graph/.
|
|
8
|
+
* stat Print node/edge counts by type and relation.
|
|
9
|
+
* query <id> Print a node's dependencies and dependents.
|
|
10
|
+
* query --type <t> List every node of one type with its dependencies
|
|
11
|
+
* (e.g. `--type embed` enumerates the embed presets).
|
|
12
|
+
* query --missing-tests List capabilities/workflows with zero inbound
|
|
13
|
+
* validates edges (see lib/graph/gaps.mjs).
|
|
14
|
+
* impacted --changed <files...> Traverse from changed files to the
|
|
15
|
+
* workflows/tests/docs they impact (see
|
|
16
|
+
* lib/graph/impacted.mjs); scopes the CI drift gate.
|
|
17
|
+
* missing-tests [--security], missing-docs, stale, dependencies, providers,
|
|
18
|
+
* surfaces Read-only gap queries over the store (see
|
|
19
|
+
* lib/graph/gap-queries.mjs). `missing-tests --security`
|
|
20
|
+
* lists workflows/embed presets with no `secures` edge.
|
|
21
|
+
* owasp OWASP GenAI Top-10 coverage matrix generated from the
|
|
22
|
+
* graph's `@owasp`-tagged security tests (LMCP-N8, see
|
|
23
|
+
* lib/graph/security-coverage.mjs).
|
|
24
|
+
* explain <workflow-id> Full ownership picture, grouped by every
|
|
25
|
+
* EDGE_RELS relation plus manifest roleChain (LMCP-C3):
|
|
26
|
+
* absent-but-expected links print as MISSING, matching
|
|
27
|
+
* `graph validate` findings. Also carries last-execution
|
|
28
|
+
* runtime evidence and execution-staleness (LMCP-C9, see
|
|
29
|
+
* lib/graph/runtime-evidence.mjs and the execution
|
|
30
|
+
* dimension in lib/graph/staleness.mjs).
|
|
10
31
|
*
|
|
11
32
|
* Seeds are read from the Construct package root (rootDir); the graph is
|
|
12
33
|
* persisted under the active project (projectDir) so it travels with .cx/.
|
|
@@ -15,7 +36,24 @@
|
|
|
15
36
|
import { buildFromRegistry } from './build-from-registry.mjs';
|
|
16
37
|
import { buildImportGraph } from './build-import-graph.mjs';
|
|
17
38
|
import { buildCoChange } from './build-co-change.mjs';
|
|
18
|
-
import {
|
|
39
|
+
import { buildFromCorpus } from './build-from-corpus.mjs';
|
|
40
|
+
import { buildFromEmbed } from './build-from-embed.mjs';
|
|
41
|
+
import { buildFromSecurity, buildOwaspMatrix, findWorkflowsMissingSecurity, OWASP_GENAI_TOP10 } from './security-coverage.mjs';
|
|
42
|
+
import { buildRuntimeEvidence } from './runtime-evidence.mjs';
|
|
43
|
+
import { writeGraph, loadGraph, dependenciesOf, dependentsOf, nodesByType, EDGE_RELS } from './store.mjs';
|
|
44
|
+
import { validateGraph } from './validate.mjs';
|
|
45
|
+
import { findMissingTestCapabilities } from './gaps.mjs';
|
|
46
|
+
import { computeImpacted } from './impacted.mjs';
|
|
47
|
+
import { computeSourceHashes, checkExecutionStaleness } from './staleness.mjs';
|
|
48
|
+
import {
|
|
49
|
+
findMissingDocs,
|
|
50
|
+
findStale,
|
|
51
|
+
findDependencies,
|
|
52
|
+
findProviders,
|
|
53
|
+
findSurfaces,
|
|
54
|
+
} from './gap-queries.mjs';
|
|
55
|
+
import { loadAllWorkflows } from '../workflows/loader.mjs';
|
|
56
|
+
import { checkWorkflowLiveness } from '../workflows/liveness.mjs';
|
|
19
57
|
|
|
20
58
|
function isoNow() {
|
|
21
59
|
return new Date().toISOString();
|
|
@@ -23,10 +61,23 @@ function isoNow() {
|
|
|
23
61
|
|
|
24
62
|
function runBuild({ rootDir, projectDir, json, coChange }) {
|
|
25
63
|
const reg = buildFromRegistry({ rootDir });
|
|
26
|
-
const
|
|
64
|
+
const corpus = buildFromCorpus({ rootDir });
|
|
65
|
+
|
|
66
|
+
// Embed presets + security tests and their annotations ship with the package,
|
|
67
|
+
// so these seeders read from rootDir like buildFromRegistry/buildFromCorpus —
|
|
68
|
+
// not projectDir, which holds only per-project runtime state (enablement,
|
|
69
|
+
// evidence) and carries no tests/ tree.
|
|
70
|
+
const embed = buildFromEmbed({ rootDir });
|
|
71
|
+
const security = buildFromSecurity({ rootDir });
|
|
72
|
+
|
|
73
|
+
// Only capability-targeted validates edges (registry + corpus) feed the
|
|
74
|
+
// import graph's realizes derivation — it maps a capability to the files its
|
|
75
|
+
// tests transitively reach. Embed/security edges have no such file
|
|
76
|
+
// realization, so they are kept in the graph but withheld from this input.
|
|
77
|
+
const validates = [...reg.edges, ...corpus.edges].filter((e) => e.rel === 'validates');
|
|
27
78
|
const imp = buildImportGraph({ rootDir, validates });
|
|
28
|
-
const nodes = [...reg.nodes, ...imp.nodes];
|
|
29
|
-
const edges = [...reg.edges, ...imp.edges];
|
|
79
|
+
const nodes = [...reg.nodes, ...corpus.nodes, ...embed.nodes, ...security.nodes, ...imp.nodes];
|
|
80
|
+
const edges = [...reg.edges, ...corpus.edges, ...embed.edges, ...security.edges, ...imp.edges];
|
|
30
81
|
|
|
31
82
|
if (coChange) {
|
|
32
83
|
const sourceRels = imp.nodes.filter((n) => n.type === 'file' || n.type === 'test').map((n) => n.name);
|
|
@@ -35,8 +86,17 @@ function runBuild({ rootDir, projectDir, json, coChange }) {
|
|
|
35
86
|
edges.push(...co.edges);
|
|
36
87
|
}
|
|
37
88
|
|
|
89
|
+
// Runtime evidence (LMCP-C9) reads persisted orchestration runs off the
|
|
90
|
+
// active project (projectDir), not the Construct package root (rootDir) —
|
|
91
|
+
// runs live under the embedding project's machine-scoped state root
|
|
92
|
+
// (ADR-0066), keyed by projectDir.
|
|
93
|
+
const evidence = buildRuntimeEvidence({ rootDir: projectDir });
|
|
94
|
+
nodes.push(...evidence.nodes);
|
|
95
|
+
edges.push(...evidence.edges);
|
|
96
|
+
|
|
38
97
|
const sourceHash = reg.sourceHash;
|
|
39
|
-
const
|
|
98
|
+
const sourceHashes = computeSourceHashes(projectDir);
|
|
99
|
+
const result = writeGraph(projectDir, { nodes, edges, generatedAt: isoNow(), sourceHash, sourceHashes });
|
|
40
100
|
const meta = loadGraph(projectDir).meta;
|
|
41
101
|
if (json) {
|
|
42
102
|
process.stdout.write(JSON.stringify({ ok: true, ...result, meta }, null, 2) + '\n');
|
|
@@ -87,6 +147,365 @@ function runQuery({ projectDir, id, json }) {
|
|
|
87
147
|
return 0;
|
|
88
148
|
}
|
|
89
149
|
|
|
150
|
+
// `query --type <t>` lists every node of one type with its outbound
|
|
151
|
+
// dependencies — the entry point the P6 acceptance uses to enumerate embed
|
|
152
|
+
// nodes (`--type embed`), but generic over any NODE_TYPES member.
|
|
153
|
+
|
|
154
|
+
function runQueryByType({ projectDir, type, json }) {
|
|
155
|
+
const graph = loadGraph(projectDir);
|
|
156
|
+
if (!graph.exists) {
|
|
157
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
158
|
+
return 1;
|
|
159
|
+
}
|
|
160
|
+
const matches = nodesByType(graph, type).map((node) => ({
|
|
161
|
+
id: node.id,
|
|
162
|
+
node,
|
|
163
|
+
dependencies: dependenciesOf(graph, node.id),
|
|
164
|
+
dependents: dependentsOf(graph, node.id),
|
|
165
|
+
}));
|
|
166
|
+
if (json) {
|
|
167
|
+
process.stdout.write(JSON.stringify({ type, count: matches.length, nodes: matches }, null, 2) + '\n');
|
|
168
|
+
return 0;
|
|
169
|
+
}
|
|
170
|
+
process.stdout.write(`${type} nodes (${matches.length}):\n`);
|
|
171
|
+
for (const m of matches) {
|
|
172
|
+
process.stdout.write(` ${m.id}\n`);
|
|
173
|
+
process.stdout.write(` → ${m.dependencies.join(', ') || '(none)'}\n`);
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// `missing-tests --security` (LMCP-N8): workflows with no inbound `secures`
|
|
179
|
+
// edge — the security-coverage gap list, distinct from the capability
|
|
180
|
+
// test-coverage gaps runMissingTests reports.
|
|
181
|
+
|
|
182
|
+
function runMissingSecurity({ projectDir, json }) {
|
|
183
|
+
const gaps = findWorkflowsMissingSecurity(projectDir);
|
|
184
|
+
if (!gaps.graphPresent) {
|
|
185
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
186
|
+
return 1;
|
|
187
|
+
}
|
|
188
|
+
if (json) {
|
|
189
|
+
process.stdout.write(JSON.stringify(gaps, null, 2) + '\n');
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
process.stdout.write(`workflows with zero linked security tests (${gaps.workflows.length}):\n`);
|
|
193
|
+
for (const id of gaps.workflows) process.stdout.write(` ${id}\n`);
|
|
194
|
+
return 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// `graph owasp` (LMCP-N8): the OWASP GenAI Top-10 coverage matrix, generated
|
|
198
|
+
// from the graph's security-test nodes. Every category is listed with its test
|
|
199
|
+
// count so uncovered categories are visible, not silently absent.
|
|
200
|
+
|
|
201
|
+
function runOwaspMatrix({ projectDir, json }) {
|
|
202
|
+
const matrix = buildOwaspMatrix(projectDir);
|
|
203
|
+
if (!matrix.graphPresent) {
|
|
204
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
if (json) {
|
|
208
|
+
process.stdout.write(JSON.stringify(matrix, null, 2) + '\n');
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
process.stdout.write(`OWASP GenAI Top-10 coverage (${OWASP_GENAI_TOP10.length} categories):\n`);
|
|
212
|
+
for (const cat of matrix.categories) {
|
|
213
|
+
const mark = cat.testCount === 0 ? '✗' : '✓';
|
|
214
|
+
process.stdout.write(` ${mark} ${cat.id} ${cat.name}: ${cat.testCount} test(s)\n`);
|
|
215
|
+
}
|
|
216
|
+
if (matrix.uncovered.length) process.stdout.write(`\nuncovered: ${matrix.uncovered.join(', ')}\n`);
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function runMissingTests({ projectDir, json }) {
|
|
221
|
+
const gaps = findMissingTestCapabilities(projectDir);
|
|
222
|
+
if (!gaps.graphPresent) {
|
|
223
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
224
|
+
return 1;
|
|
225
|
+
}
|
|
226
|
+
if (json) {
|
|
227
|
+
process.stdout.write(JSON.stringify(gaps, null, 2) + '\n');
|
|
228
|
+
return 0;
|
|
229
|
+
}
|
|
230
|
+
process.stdout.write(`capabilities with zero validating tests (${gaps.capabilities.length}):\n`);
|
|
231
|
+
for (const id of gaps.capabilities) process.stdout.write(` ${id}\n`);
|
|
232
|
+
process.stdout.write(`workflows with zero validated embedding capability (${gaps.workflows.length}):\n`);
|
|
233
|
+
for (const id of gaps.workflows) process.stdout.write(` ${id}\n`);
|
|
234
|
+
return 0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function parseChangedArg(args) {
|
|
238
|
+
const idx = args.indexOf('--changed');
|
|
239
|
+
if (idx === -1) return [];
|
|
240
|
+
const rest = args.slice(idx + 1);
|
|
241
|
+
const files = [];
|
|
242
|
+
for (const a of rest) {
|
|
243
|
+
if (a.startsWith('--')) break;
|
|
244
|
+
files.push(a);
|
|
245
|
+
}
|
|
246
|
+
return files;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function runImpacted(args, { projectDir, json }) {
|
|
250
|
+
const changed = parseChangedArg(args);
|
|
251
|
+
if (changed.length === 0) {
|
|
252
|
+
process.stderr.write('Usage: construct graph impacted --changed <file...> [--json]\n');
|
|
253
|
+
return 1;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const result = computeImpacted({ rootDir: projectDir, changedFiles: changed });
|
|
257
|
+
if (!result.graphPresent) {
|
|
258
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
259
|
+
return 1;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (json) {
|
|
263
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
process.stdout.write(`Changed: ${result.changed.length} file(s)\n`);
|
|
268
|
+
process.stdout.write(`Impacted workflows (${result.impactedWorkflows.length}): ${result.impactedWorkflows.join(', ') || '(none)'}\n`);
|
|
269
|
+
process.stdout.write(`Impacted capabilities (${result.impactedCapabilities.length}): ${result.impactedCapabilities.join(', ') || '(none)'}\n`);
|
|
270
|
+
process.stdout.write(`Impacted tests (${result.impactedTests.length}):\n`);
|
|
271
|
+
for (const t of result.impactedTests) process.stdout.write(` ${t}\n`);
|
|
272
|
+
process.stdout.write(`Impacted docs (${result.impactedDocs.length}):\n`);
|
|
273
|
+
for (const d of result.impactedDocs) process.stdout.write(` ${d}\n`);
|
|
274
|
+
if (result.unknown.length) {
|
|
275
|
+
process.stdout.write(`\n⚠ Not in graph (${result.unknown.length}): ${result.unknown.join(', ')}\n`);
|
|
276
|
+
}
|
|
277
|
+
return 0;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// `explain` renders every EDGE_RELS relation as its own question section
|
|
281
|
+
// (LMCP-C3) so a reader sees the workflow's full ownership picture — not just
|
|
282
|
+
// dependencies/providers/surfaces — in one call. A workflow node itself only
|
|
283
|
+
// carries direct `embeds` (inbound, from its capability) and `documents`
|
|
284
|
+
// (inbound, from docs) edges (see build-from-registry.mjs); every other
|
|
285
|
+
// relation a workflow can meaningfully answer (uses/governed_by/requires/
|
|
286
|
+
// exposes/reads) lives on the capability that embeds the workflow, so those
|
|
287
|
+
// sections reuse the same embeddingCapabilities aggregation gap-queries.mjs
|
|
288
|
+
// already computes (via findDependencies/findProviders/findSurfaces) rather
|
|
289
|
+
// than re-deriving it. Relations that are structurally file/module/specialist
|
|
290
|
+
// -level (imports, realizes, covers, contains, co_changes, owned_by,
|
|
291
|
+
// validates) never land on a workflow node in this schema (see
|
|
292
|
+
// build-from-registry.mjs's edge-shape comment) and are rendered
|
|
293
|
+
// `applicable: false` rather than MISSING — an absent link only signals a gap
|
|
294
|
+
// when the relation could exist.
|
|
295
|
+
// `roleChain` is manifest data, not a graph edge — EDGE_RELS has no roleChain
|
|
296
|
+
// member — so it is rendered as its own manifest-sourced section, resolved
|
|
297
|
+
// against the same checkWorkflowLiveness violations `graph validate` reports,
|
|
298
|
+
// so a MISSING/violation marker here always has a matching validate finding.
|
|
299
|
+
|
|
300
|
+
const WORKFLOW_LEVEL_RELS = new Set(['embeds', 'documents', 'uses', 'governed_by', 'requires', 'exposes', 'reads']);
|
|
301
|
+
|
|
302
|
+
function relLabel(rel) {
|
|
303
|
+
return rel === 'requires' ? 'requires (via provider)' : rel;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Builds one section per EDGE_RELS member. Relations sourced directly off the
|
|
307
|
+
// workflow node (embeds inbound, documents inbound) read the live graph;
|
|
308
|
+
// relations that only ever land on the embedding capability (uses,
|
|
309
|
+
// governed_by, requires, exposes, reads) reuse the gap-query aggregations so
|
|
310
|
+
// `explain` never disagrees with `dependencies`/`providers`/`surfaces`/
|
|
311
|
+
// `missing-docs`. A relation with zero applicable edges is reported as
|
|
312
|
+
// MISSING so the human/agent reader sees an explicit gap instead of a quietly
|
|
313
|
+
// empty list.
|
|
314
|
+
|
|
315
|
+
function buildEdgeSections(graph, workflowId, projectDir) {
|
|
316
|
+
const deps = findDependencies(projectDir).workflows[workflowId] || { contracts: [], uses: [], requires: [] };
|
|
317
|
+
const surfaces = findSurfaces(projectDir).workflows[workflowId] || [];
|
|
318
|
+
const docs = findMissingDocs(projectDir);
|
|
319
|
+
const embeddingCaps = dependentsOf(graph, workflowId, 'embeds');
|
|
320
|
+
const reads = new Set();
|
|
321
|
+
for (const capId of embeddingCaps) {
|
|
322
|
+
for (const providerId of dependenciesOf(graph, capId, 'uses').filter((i) => i.startsWith('provider:'))) {
|
|
323
|
+
for (const specId of dependentsOf(graph, providerId, 'reads')) reads.add(specId);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const values = {
|
|
328
|
+
embeds: embeddingCaps,
|
|
329
|
+
documents: dependentsOf(graph, workflowId, 'documents'),
|
|
330
|
+
uses: deps.uses,
|
|
331
|
+
governed_by: deps.contracts,
|
|
332
|
+
requires: deps.requires,
|
|
333
|
+
exposes: surfaces,
|
|
334
|
+
reads: [...reads].sort(),
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const sections = [];
|
|
338
|
+
for (const rel of EDGE_RELS) {
|
|
339
|
+
const applicable = WORKFLOW_LEVEL_RELS.has(rel);
|
|
340
|
+
const links = applicable ? (values[rel] ?? []) : [];
|
|
341
|
+
sections.push({
|
|
342
|
+
rel,
|
|
343
|
+
label: relLabel(rel),
|
|
344
|
+
links,
|
|
345
|
+
missing: applicable && links.length === 0,
|
|
346
|
+
applicable,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
// documents doubles as the missing-docs consistency check (LMCP-C5): a
|
|
350
|
+
// workflow with zero inbound documents edges is already reported by
|
|
351
|
+
// `graph missing-docs`, so mirror that exact membership test here.
|
|
352
|
+
const documentsSection = sections.find((s) => s.rel === 'documents');
|
|
353
|
+
if (documentsSection) documentsSection.missing = docs.workflows.includes(workflowId);
|
|
354
|
+
|
|
355
|
+
return sections;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// roleChain is manifest-only (not an EDGE_RELS member): resolved against the
|
|
359
|
+
// merged workflow-manifest set and checkWorkflowLiveness so a MISSING/broken
|
|
360
|
+
// roleChain entry here always has a matching `graph validate` violation
|
|
361
|
+
// string to cross-check against (the bead's consistency-test requirement).
|
|
362
|
+
|
|
363
|
+
function buildRoleChainSection(workflowType, projectDir) {
|
|
364
|
+
const { workflows } = loadAllWorkflows({ rootDir: projectDir });
|
|
365
|
+
const manifest = workflows.find((m) => m.id === workflowType);
|
|
366
|
+
const { violations } = checkWorkflowLiveness(workflows, { rootDir: projectDir });
|
|
367
|
+
const ownViolations = violations.filter((v) => v.includes(`'${workflowType}'`) || (manifest?._filePath && v.startsWith(manifest._filePath)));
|
|
368
|
+
const roleChain = Array.isArray(manifest?.roleChain) ? manifest.roleChain : [];
|
|
369
|
+
return {
|
|
370
|
+
rel: 'roleChain',
|
|
371
|
+
label: 'roleChain',
|
|
372
|
+
links: roleChain,
|
|
373
|
+
missing: roleChain.length === 0,
|
|
374
|
+
applicable: true,
|
|
375
|
+
violations: ownViolations,
|
|
376
|
+
provenance: manifest
|
|
377
|
+
? {
|
|
378
|
+
source: manifest._source || 'unknown',
|
|
379
|
+
filePath: manifest._filePath || null,
|
|
380
|
+
shadows: manifest._shadowedBy || [],
|
|
381
|
+
}
|
|
382
|
+
: null,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function runExplain(args, { projectDir, json }) {
|
|
387
|
+
const id = args.slice(1).find((a) => !a.startsWith('--'));
|
|
388
|
+
if (!id) {
|
|
389
|
+
process.stderr.write('Usage: construct graph explain <workflow-id> [--json]\n');
|
|
390
|
+
return 1;
|
|
391
|
+
}
|
|
392
|
+
const workflowId = id.startsWith('workflow:') ? id : `workflow:${id}`;
|
|
393
|
+
const workflowType = workflowId.slice('workflow:'.length);
|
|
394
|
+
|
|
395
|
+
const graph = loadGraph(projectDir);
|
|
396
|
+
if (!graph.exists) {
|
|
397
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
398
|
+
return 1;
|
|
399
|
+
}
|
|
400
|
+
const node = graph.nodes.get(workflowId);
|
|
401
|
+
if (!node) {
|
|
402
|
+
process.stderr.write(`workflow not found in graph: ${workflowId}\n`);
|
|
403
|
+
return 1;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const deps = findDependencies(projectDir).workflows[workflowId] || { contracts: [], uses: [], requires: [] };
|
|
407
|
+
const providers = findProviders(projectDir).workflows[workflowId] || [];
|
|
408
|
+
const surfaces = findSurfaces(projectDir).workflows[workflowId] || [];
|
|
409
|
+
const sections = buildEdgeSections(graph, workflowId, projectDir);
|
|
410
|
+
const roleChainSection = buildRoleChainSection(workflowType, projectDir);
|
|
411
|
+
const allSections = [...sections, roleChainSection];
|
|
412
|
+
|
|
413
|
+
const execution = checkExecutionStaleness(projectDir);
|
|
414
|
+
const executionState = execution.workflows[workflowType] || {
|
|
415
|
+
lastExecution: null, neverExecuted: true, stale: false, ageDays: null, thresholdDays: null,
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const result = {
|
|
419
|
+
id: workflowId,
|
|
420
|
+
node,
|
|
421
|
+
dependencies: deps,
|
|
422
|
+
providers,
|
|
423
|
+
surfaces,
|
|
424
|
+
sections: allSections,
|
|
425
|
+
missing: allSections.filter((s) => s.missing).map((s) => s.rel),
|
|
426
|
+
execution: executionState,
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
if (json) {
|
|
430
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
431
|
+
return 0;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
process.stdout.write(`${workflowId}\n`);
|
|
435
|
+
for (const section of allSections) {
|
|
436
|
+
if (!section.applicable) continue;
|
|
437
|
+
if (section.missing) {
|
|
438
|
+
process.stdout.write(` ${section.label}: MISSING\n`);
|
|
439
|
+
} else {
|
|
440
|
+
process.stdout.write(` ${section.label} (${section.links.length}): ${section.links.join(', ') || '(none)'}\n`);
|
|
441
|
+
}
|
|
442
|
+
if (section.violations?.length) {
|
|
443
|
+
for (const v of section.violations) process.stdout.write(` ! ${v}\n`);
|
|
444
|
+
}
|
|
445
|
+
if (section.provenance) {
|
|
446
|
+
process.stdout.write(` source: ${section.provenance.source} (${section.provenance.filePath || 'n/a'})\n`);
|
|
447
|
+
for (const shadow of section.provenance.shadows) {
|
|
448
|
+
process.stdout.write(` overrides: ${shadow.source} (${shadow.filePath || 'n/a'})\n`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
process.stdout.write(` contracts (${deps.contracts.length}): ${deps.contracts.join(', ') || '(none)'}\n`);
|
|
453
|
+
process.stdout.write(` uses (${deps.uses.length}): ${deps.uses.join(', ') || '(none)'}\n`);
|
|
454
|
+
process.stdout.write(` requires (${deps.requires.length}): ${deps.requires.join(', ') || '(none)'}\n`);
|
|
455
|
+
process.stdout.write(` providers (${providers.length}): ${providers.join(', ') || '(none)'}\n`);
|
|
456
|
+
process.stdout.write(` surfaces (${surfaces.length}): ${surfaces.join(', ') || '(none)'}\n`);
|
|
457
|
+
if (executionState.neverExecuted) {
|
|
458
|
+
process.stdout.write(' execution: NEVER EXECUTED — no runtime evidence found\n');
|
|
459
|
+
} else {
|
|
460
|
+
const { lastExecution, stale, ageDays: age, thresholdDays } = executionState;
|
|
461
|
+
process.stdout.write(` execution: last run ${lastExecution.timestamp} (outcome: ${lastExecution.outcome}, run ${lastExecution.runId})\n`);
|
|
462
|
+
process.stdout.write(` age: ${age?.toFixed(1)}d / threshold: ${thresholdDays}d — ${stale ? 'STALE' : 'fresh'}\n`);
|
|
463
|
+
}
|
|
464
|
+
return 0;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Uniform runner for the six read-only gap-query subcommands (C5): each
|
|
468
|
+
// query function takes projectDir and returns { graphPresent, ...payload };
|
|
469
|
+
// --json dumps the payload verbatim, non-JSON falls back to the same shape
|
|
470
|
+
// so a human running the command still sees structured field names.
|
|
471
|
+
|
|
472
|
+
function runGapQuery(queryFn, { projectDir, json }) {
|
|
473
|
+
const result = queryFn(projectDir);
|
|
474
|
+
if (!result.graphPresent) {
|
|
475
|
+
process.stderr.write('No graph found. Run `construct graph build` first.\n');
|
|
476
|
+
return 1;
|
|
477
|
+
}
|
|
478
|
+
if (json) {
|
|
479
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
482
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function runValidate(args, { rootDir, projectDir }) {
|
|
487
|
+
const strict = args.includes('--strict');
|
|
488
|
+
const json = args.includes('--json');
|
|
489
|
+
const result = validateGraph(projectDir || rootDir, { strict });
|
|
490
|
+
|
|
491
|
+
if (json) {
|
|
492
|
+
console.log(JSON.stringify(result, null, 2));
|
|
493
|
+
} else {
|
|
494
|
+
for (const err of result.errors) console.error(` error: ${err}`);
|
|
495
|
+
for (const warn of result.warnings) console.error(` warn: ${warn}`);
|
|
496
|
+
for (const info of result.infos) console.log(` info: ${info}`);
|
|
497
|
+
if (result.errors.length > 0) {
|
|
498
|
+
console.error(`\n ✖ ${result.errors.length} error(s), ${result.warnings.length} warning(s)`);
|
|
499
|
+
} else if (result.warnings.length > 0) {
|
|
500
|
+
console.log(`\n ✓ ${result.warnings.length} warning(s)`);
|
|
501
|
+
} else {
|
|
502
|
+
console.log('\n ✓ graph is valid');
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
process.exit(result.errors.length > 0 ? 1 : 0);
|
|
507
|
+
}
|
|
508
|
+
|
|
90
509
|
/**
|
|
91
510
|
* @param {string[]} args
|
|
92
511
|
* @param {{ rootDir: string, projectDir: string }} ctx
|
|
@@ -98,13 +517,38 @@ export function runGraphCli(args, { rootDir, projectDir }) {
|
|
|
98
517
|
if (sub === 'build') return runBuild({ rootDir, projectDir, json, coChange: !args.includes('--no-co-change') });
|
|
99
518
|
if (sub === 'stat') return runStat({ projectDir, json });
|
|
100
519
|
if (sub === 'query') {
|
|
520
|
+
if (args.includes('--missing-tests')) return runMissingTests({ projectDir, json });
|
|
521
|
+
const typeIdx = args.indexOf('--type');
|
|
522
|
+
if (typeIdx !== -1) {
|
|
523
|
+
const type = args[typeIdx + 1];
|
|
524
|
+
if (!type || type.startsWith('--')) {
|
|
525
|
+
process.stderr.write('Usage: construct graph query --type <node-type> [--json]\n');
|
|
526
|
+
return 1;
|
|
527
|
+
}
|
|
528
|
+
return runQueryByType({ projectDir, type, json });
|
|
529
|
+
}
|
|
101
530
|
const id = args.slice(1).find((a) => !a.startsWith('--'));
|
|
102
531
|
if (!id) {
|
|
103
|
-
process.stderr.write('Usage: construct
|
|
532
|
+
process.stderr.write('Usage: construct graph query <node-id> | --type <node-type> [--json]\n');
|
|
104
533
|
return 1;
|
|
105
534
|
}
|
|
106
535
|
return runQuery({ projectDir, id, json });
|
|
107
536
|
}
|
|
108
|
-
|
|
537
|
+
if (sub === 'validate') {
|
|
538
|
+
return runValidate(args, { rootDir, projectDir });
|
|
539
|
+
}
|
|
540
|
+
if (sub === 'impacted') return runImpacted(args, { projectDir, json });
|
|
541
|
+
if (sub === 'owasp') return runOwaspMatrix({ projectDir, json });
|
|
542
|
+
if (sub === 'missing-tests') {
|
|
543
|
+
if (args.includes('--security')) return runMissingSecurity({ projectDir, json });
|
|
544
|
+
return runMissingTests({ projectDir, json });
|
|
545
|
+
}
|
|
546
|
+
if (sub === 'missing-docs') return runGapQuery(findMissingDocs, { projectDir, json });
|
|
547
|
+
if (sub === 'stale') return runGapQuery(findStale, { projectDir, json });
|
|
548
|
+
if (sub === 'dependencies') return runGapQuery(findDependencies, { projectDir, json });
|
|
549
|
+
if (sub === 'providers') return runGapQuery(findProviders, { projectDir, json });
|
|
550
|
+
if (sub === 'surfaces') return runGapQuery(findSurfaces, { projectDir, json });
|
|
551
|
+
if (sub === 'explain') return runExplain(args, { projectDir, json });
|
|
552
|
+
process.stderr.write(`Unknown graph subcommand: ${sub}. Available: build, stat, query, validate, impacted, missing-tests, missing-docs, stale, dependencies, providers, surfaces, explain\n`);
|
|
109
553
|
return 1;
|
|
110
554
|
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/gap-queries.mjs — read-only gap queries over the living graph (LMCP-C5).
|
|
3
|
+
*
|
|
4
|
+
* Six queries answer "what does this workflow/capability still need":
|
|
5
|
+
* missing-docs (workflow/provider with zero inbound documents edges), stale
|
|
6
|
+
* (per-source seed drift via lib/graph/staleness.mjs — LMCP-C6), dependencies
|
|
7
|
+
* (per-workflow requires-edges: capability → provider/tool), providers
|
|
8
|
+
* (per-workflow provider requirements via capability uses-edges to provider
|
|
9
|
+
* nodes), and surfaces (per-workflow surface requirements via capability
|
|
10
|
+
* exposes-edges). missing-tests lives in lib/graph/gaps.mjs and is re-exported
|
|
11
|
+
* here for a single import surface; validateGraph (lib/graph/validate.mjs)
|
|
12
|
+
* sources its own missing-tests warnings from the same findMissingTestCapabilities
|
|
13
|
+
* function so `graph missing-tests` and `graph validate` never disagree.
|
|
14
|
+
* Every query is read-only: it inspects the persisted store and never
|
|
15
|
+
* mutates it.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { loadGraph, nodesByType, dependenciesOf, dependentsOf } from './store.mjs';
|
|
19
|
+
import { checkGraphStaleness } from './staleness.mjs';
|
|
20
|
+
import { findMissingTestCapabilities } from './gaps.mjs';
|
|
21
|
+
|
|
22
|
+
export { findMissingTestCapabilities as findMissingTests };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Workflows and providers with zero inbound `documents` edges.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} rootDir
|
|
28
|
+
* @returns {{ graphPresent: boolean, workflows: string[], providers: string[] }}
|
|
29
|
+
*/
|
|
30
|
+
export function findMissingDocs(rootDir) {
|
|
31
|
+
const graph = loadGraph(rootDir);
|
|
32
|
+
if (!graph.exists) return { graphPresent: false, workflows: [], providers: [] };
|
|
33
|
+
|
|
34
|
+
const workflows = nodesByType(graph, 'workflow')
|
|
35
|
+
.filter((wf) => dependentsOf(graph, wf.id, 'documents').length === 0)
|
|
36
|
+
.map((wf) => wf.id)
|
|
37
|
+
.sort();
|
|
38
|
+
|
|
39
|
+
const providers = nodesByType(graph, 'provider')
|
|
40
|
+
.filter((p) => dependentsOf(graph, p.id, 'documents').length === 0)
|
|
41
|
+
.map((p) => p.id)
|
|
42
|
+
.sort();
|
|
43
|
+
|
|
44
|
+
return { graphPresent: true, workflows, providers };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Per-source seed-hash staleness (LMCP-C6): which named seed source(s), if
|
|
49
|
+
* any, changed since the last `construct graph build`.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} rootDir
|
|
52
|
+
* @returns {{ graphPresent: boolean, stale: boolean, staleSources: string[], staleReason: string|null }}
|
|
53
|
+
*/
|
|
54
|
+
export function findStale(rootDir) {
|
|
55
|
+
const state = checkGraphStaleness(rootDir);
|
|
56
|
+
return {
|
|
57
|
+
graphPresent: state.present,
|
|
58
|
+
stale: state.stale,
|
|
59
|
+
staleSources: state.staleSources || [],
|
|
60
|
+
staleReason: state.staleReason,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// A workflow's requirements are gathered via its embedding capabilities: a
|
|
65
|
+
// capability --embeds--> workflow edge is stored in the forward direction
|
|
66
|
+
// (capability -> workflow), so the workflow's capabilities are its
|
|
67
|
+
// dependents along 'embeds', not its dependencies.
|
|
68
|
+
|
|
69
|
+
function embeddingCapabilities(graph, workflowId) {
|
|
70
|
+
return dependentsOf(graph, workflowId, 'embeds');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Per-workflow declared dependencies: the union, over every capability that
|
|
75
|
+
* embeds the workflow, of contracts (governed_by), skills/rules (uses), and
|
|
76
|
+
* provider tool requirements (requires).
|
|
77
|
+
*
|
|
78
|
+
* @param {string} rootDir
|
|
79
|
+
* @returns {{ graphPresent: boolean, workflows: Record<string, { contracts: string[], uses: string[], requires: string[] }> }}
|
|
80
|
+
*/
|
|
81
|
+
export function findDependencies(rootDir) {
|
|
82
|
+
const graph = loadGraph(rootDir);
|
|
83
|
+
if (!graph.exists) return { graphPresent: false, workflows: {} };
|
|
84
|
+
|
|
85
|
+
const workflows = {};
|
|
86
|
+
for (const wf of nodesByType(graph, 'workflow')) {
|
|
87
|
+
const caps = embeddingCapabilities(graph, wf.id);
|
|
88
|
+
const contracts = new Set();
|
|
89
|
+
const uses = new Set();
|
|
90
|
+
const requires = new Set();
|
|
91
|
+
for (const capId of caps) {
|
|
92
|
+
for (const id of dependenciesOf(graph, capId, 'governed_by')) contracts.add(id);
|
|
93
|
+
for (const id of dependenciesOf(graph, capId, 'uses')) uses.add(id);
|
|
94
|
+
for (const providerId of dependenciesOf(graph, capId, 'uses').filter((id) => id.startsWith('provider:'))) {
|
|
95
|
+
for (const toolId of dependenciesOf(graph, providerId, 'requires')) requires.add(toolId);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
workflows[wf.id] = {
|
|
99
|
+
contracts: [...contracts].sort(),
|
|
100
|
+
uses: [...uses].sort(),
|
|
101
|
+
requires: [...requires].sort(),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return { graphPresent: true, workflows };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Per-workflow provider requirements: providers named by any embedding
|
|
110
|
+
* capability's `uses` edges.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} rootDir
|
|
113
|
+
* @returns {{ graphPresent: boolean, workflows: Record<string, string[]> }}
|
|
114
|
+
*/
|
|
115
|
+
export function findProviders(rootDir) {
|
|
116
|
+
const graph = loadGraph(rootDir);
|
|
117
|
+
if (!graph.exists) return { graphPresent: false, workflows: {} };
|
|
118
|
+
|
|
119
|
+
const workflows = {};
|
|
120
|
+
for (const wf of nodesByType(graph, 'workflow')) {
|
|
121
|
+
const caps = embeddingCapabilities(graph, wf.id);
|
|
122
|
+
const providers = new Set();
|
|
123
|
+
for (const capId of caps) {
|
|
124
|
+
for (const id of dependenciesOf(graph, capId, 'uses')) {
|
|
125
|
+
if (id.startsWith('provider:')) providers.add(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
workflows[wf.id] = [...providers].sort();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { graphPresent: true, workflows };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Per-workflow surface requirements: surfaces exposed by any embedding
|
|
136
|
+
* capability.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} rootDir
|
|
139
|
+
* @returns {{ graphPresent: boolean, workflows: Record<string, string[]> }}
|
|
140
|
+
*/
|
|
141
|
+
export function findSurfaces(rootDir) {
|
|
142
|
+
const graph = loadGraph(rootDir);
|
|
143
|
+
if (!graph.exists) return { graphPresent: false, workflows: {} };
|
|
144
|
+
|
|
145
|
+
const workflows = {};
|
|
146
|
+
for (const wf of nodesByType(graph, 'workflow')) {
|
|
147
|
+
const caps = embeddingCapabilities(graph, wf.id);
|
|
148
|
+
const surfaces = new Set();
|
|
149
|
+
for (const capId of caps) {
|
|
150
|
+
for (const id of dependenciesOf(graph, capId, 'exposes')) surfaces.add(id);
|
|
151
|
+
}
|
|
152
|
+
workflows[wf.id] = [...surfaces].sort();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { graphPresent: true, workflows };
|
|
156
|
+
}
|