@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
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/gaps.mjs — coverage-gap queries over the living dependency graph.
|
|
3
|
+
*
|
|
4
|
+
* findMissingTestCapabilities() answers "which capabilities/workflows have
|
|
5
|
+
* zero validating tests" by reading the persisted graph's inbound validates
|
|
6
|
+
* edges — the C1 store already dedups test --validates--> capability edges
|
|
7
|
+
* from both registry (verification.functional/hostEmulation) and
|
|
8
|
+
* corpus-annotation (@capability tags) sources, so a zero-count here is a
|
|
9
|
+
* real gap, not a source-coverage artifact. A workflow is flagged only when
|
|
10
|
+
* every capability that embeds it also has zero validating tests, since a
|
|
11
|
+
* partially-tested workflow is a weaker signal than an untested capability.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { loadGraph, nodesByType, dependentsOf, dependenciesOf } from './store.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} rootDir
|
|
18
|
+
* @returns {{ graphPresent: boolean, capabilities: string[], workflows: string[] }}
|
|
19
|
+
*/
|
|
20
|
+
export function findMissingTestCapabilities(rootDir) {
|
|
21
|
+
const graph = loadGraph(rootDir);
|
|
22
|
+
if (!graph.exists) return { graphPresent: false, capabilities: [], workflows: [] };
|
|
23
|
+
|
|
24
|
+
const untestedCapIds = new Set();
|
|
25
|
+
for (const cap of nodesByType(graph, 'capability')) {
|
|
26
|
+
if (dependentsOf(graph, cap.id, 'validates').length === 0) untestedCapIds.add(cap.id);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const untestedWorkflows = [];
|
|
30
|
+
for (const wf of nodesByType(graph, 'workflow')) {
|
|
31
|
+
const embeddingCaps = dependentsOf(graph, wf.id, 'embeds');
|
|
32
|
+
if (embeddingCaps.length === 0) continue;
|
|
33
|
+
if (embeddingCaps.every((capId) => untestedCapIds.has(capId))) untestedWorkflows.push(wf.id);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
graphPresent: true,
|
|
38
|
+
capabilities: [...untestedCapIds].sort(),
|
|
39
|
+
workflows: untestedWorkflows.sort(),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/impacted.mjs — change-impact traversal for `construct graph impacted`.
|
|
3
|
+
*
|
|
4
|
+
* Given a set of changed repo-relative file paths, walks the living
|
|
5
|
+
* dependency graph forward from each changed-file node through `imports`
|
|
6
|
+
* (reverse — files/tests that import the changed file), `realizes`
|
|
7
|
+
* (changed file → capability), `embeds` (capability → workflow), `validates`
|
|
8
|
+
* (test → capability, reverse to find tests), and `documents` (doc → workflow
|
|
9
|
+
* or provider, reverse to find docs) edges to answer: which workflows, tests,
|
|
10
|
+
* and docs does this changeset touch. Scopes which checks a PR must run for a
|
|
11
|
+
* CI drift gate. A changed path absent from the graph is reported in
|
|
12
|
+
* `unknown` rather than raising — an unrecognized file (renamed, deleted,
|
|
13
|
+
* generated, or simply not yet built into the graph) is common and must not
|
|
14
|
+
* crash the traversal.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { loadGraph, dependenciesOf, dependentsOf } from './store.mjs';
|
|
18
|
+
|
|
19
|
+
function toFileNodeId(rel) {
|
|
20
|
+
return rel.endsWith('.test.mjs') || rel.endsWith('.test.js') ? `test:${rel}` : `file:${rel}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function normalizeChangedPaths(changedFiles) {
|
|
24
|
+
return [...new Set((changedFiles || []).map((f) => f.split('\\').join('/').replace(/^\.\//, '')))].filter(Boolean);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Reverse-import closure: every file/test/module that transitively imports
|
|
28
|
+
// the changed node, so a leaf-file edit surfaces every consumer up the chain.
|
|
29
|
+
|
|
30
|
+
function reverseImportClosure(graph, startId) {
|
|
31
|
+
const reached = new Set();
|
|
32
|
+
const stack = [startId];
|
|
33
|
+
while (stack.length) {
|
|
34
|
+
const id = stack.pop();
|
|
35
|
+
for (const importer of dependentsOf(graph, id, 'imports')) {
|
|
36
|
+
if (reached.has(importer)) continue;
|
|
37
|
+
reached.add(importer);
|
|
38
|
+
stack.push(importer);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return reached;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function stripPrefix(id) {
|
|
45
|
+
const colon = id.indexOf(':');
|
|
46
|
+
return colon === -1 ? id : id.slice(colon + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {object} opts
|
|
51
|
+
* @param {string} opts.rootDir — project root holding .cx/graph/.
|
|
52
|
+
* @param {string[]} opts.changedFiles — repo-relative paths.
|
|
53
|
+
* @returns {{
|
|
54
|
+
* graphPresent: boolean,
|
|
55
|
+
* changed: string[],
|
|
56
|
+
* unknown: string[],
|
|
57
|
+
* impactedWorkflows: string[],
|
|
58
|
+
* impactedTests: string[],
|
|
59
|
+
* impactedDocs: string[],
|
|
60
|
+
* impactedCapabilities: string[],
|
|
61
|
+
* }}
|
|
62
|
+
*/
|
|
63
|
+
export function computeImpacted({ rootDir, changedFiles }) {
|
|
64
|
+
const changed = normalizeChangedPaths(changedFiles);
|
|
65
|
+
const graph = loadGraph(rootDir);
|
|
66
|
+
|
|
67
|
+
if (!graph.exists) {
|
|
68
|
+
return {
|
|
69
|
+
graphPresent: false,
|
|
70
|
+
changed,
|
|
71
|
+
unknown: changed,
|
|
72
|
+
impactedWorkflows: [],
|
|
73
|
+
impactedTests: [],
|
|
74
|
+
impactedDocs: [],
|
|
75
|
+
impactedCapabilities: [],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const unknown = [];
|
|
80
|
+
const capabilities = new Set();
|
|
81
|
+
const tests = new Set();
|
|
82
|
+
const workflows = new Set();
|
|
83
|
+
const docs = new Set();
|
|
84
|
+
|
|
85
|
+
for (const rel of changed) {
|
|
86
|
+
const id = toFileNodeId(rel);
|
|
87
|
+
if (!graph.nodes.has(id)) { unknown.push(rel); continue; }
|
|
88
|
+
|
|
89
|
+
if (id.startsWith('test:')) tests.add(id);
|
|
90
|
+
|
|
91
|
+
for (const consumer of reverseImportClosure(graph, id)) {
|
|
92
|
+
if (consumer.startsWith('test:')) tests.add(consumer);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
for (const cap of dependenciesOf(graph, id, 'realizes')) capabilities.add(cap);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// A capability reached via realizes pulls in its validating tests and its
|
|
99
|
+
// embedded workflow; a test reached via the import closure pulls in every
|
|
100
|
+
// capability it validates, closing the loop in either direction.
|
|
101
|
+
|
|
102
|
+
for (const cap of [...capabilities]) {
|
|
103
|
+
for (const t of dependentsOf(graph, cap, 'validates')) tests.add(t);
|
|
104
|
+
for (const wf of dependenciesOf(graph, cap, 'embeds')) workflows.add(wf);
|
|
105
|
+
}
|
|
106
|
+
for (const t of tests) {
|
|
107
|
+
for (const cap of dependenciesOf(graph, t, 'validates')) {
|
|
108
|
+
capabilities.add(cap);
|
|
109
|
+
for (const wf of dependenciesOf(graph, cap, 'embeds')) workflows.add(wf);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Docs point at what they document (doc --documents--> workflow|provider),
|
|
114
|
+
// so a doc is impacted when it documents a workflow already in scope.
|
|
115
|
+
|
|
116
|
+
for (const wf of workflows) {
|
|
117
|
+
for (const doc of dependentsOf(graph, wf, 'documents')) docs.add(doc);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
graphPresent: true,
|
|
122
|
+
changed,
|
|
123
|
+
unknown: unknown.sort(),
|
|
124
|
+
impactedWorkflows: [...workflows].map(stripPrefix).sort(),
|
|
125
|
+
impactedTests: [...tests].map(stripPrefix).sort(),
|
|
126
|
+
impactedDocs: [...docs].map(stripPrefix).sort(),
|
|
127
|
+
impactedCapabilities: [...capabilities].map(stripPrefix).sort(),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/runtime-evidence.mjs — runtime-evidence edges: traces linked to workflow nodes (LMCP-C9).
|
|
3
|
+
*
|
|
4
|
+
* The living graph has static edges only (registry, import-graph, co-change,
|
|
5
|
+
* corpus-annotation) — a workflow can be green in every one of those and still
|
|
6
|
+
* never have executed successfully. This module closes that gap by reading
|
|
7
|
+
* persisted orchestration run records (`<stateRoot>/runtime/orchestration/runs/*.json`,
|
|
8
|
+
* ADR-0066's machine-scoped location for the Mode-A filesystem run store
|
|
9
|
+
* lib/orchestration/run-store.mjs writes to) read-only and mapping each run
|
|
10
|
+
* that resolves to a workflow type onto a
|
|
11
|
+
* `runtime-evidence:<runId> --evidenced_by--> workflow:<type>` edge, carrying
|
|
12
|
+
* `{ timestamp, outcome }` on the runtime-evidence node's attrs. One node per
|
|
13
|
+
* run (not one node per workflow) so repeated runs of the same workflow each
|
|
14
|
+
* leave their own evidence rather than collapsing into a single edge whose
|
|
15
|
+
* weight-summing (store.mjs normalizeEdges) would blur distinct timestamps.
|
|
16
|
+
*
|
|
17
|
+
* A run's workflow type is `run.request.workflowType`, falling back to
|
|
18
|
+
* `run.plan.suggestedWorkflowType` (the same fallback lib/orchestration/
|
|
19
|
+
* run-store.mjs's listRuns already exposes) — a run with neither is not
|
|
20
|
+
* attributable to a workflow node and is skipped, not fabricated onto one.
|
|
21
|
+
* Outcome favors the F4-aggregated `run.executionState` (executed |
|
|
22
|
+
* degraded-executed | prepared | failed) when present since it already
|
|
23
|
+
* reconciles every task's signal; a legacy run with no executionState falls
|
|
24
|
+
* back to a coarser mapping off `run.status`.
|
|
25
|
+
*
|
|
26
|
+
* `latestEvidenceByWorkflow` reduces the per-run nodes to one summary per
|
|
27
|
+
* workflow type (most recent timestamp wins) — the shape `graph explain` and
|
|
28
|
+
* the C6 execution-staleness dimension (staleness.mjs) both consume, so a
|
|
29
|
+
* workflow's "last execution" is defined in exactly one place.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
33
|
+
import path from 'node:path';
|
|
34
|
+
|
|
35
|
+
import { nodeId } from './store.mjs';
|
|
36
|
+
import { listWorkflowDefs } from '../embedded-contract/workflow-defs.mjs';
|
|
37
|
+
import { runtimeDir } from '../orchestration/run-store.mjs';
|
|
38
|
+
|
|
39
|
+
function runsDir(rootDir) {
|
|
40
|
+
return path.join(runtimeDir(rootDir), 'runs');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read every persisted run record. Read-only — this module never writes to
|
|
45
|
+
* the orchestration run store. A corrupt run file is skipped, not fatal.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} rootDir
|
|
48
|
+
* @returns {object[]}
|
|
49
|
+
*/
|
|
50
|
+
export function readRunRecords(rootDir) {
|
|
51
|
+
const dir = runsDir(rootDir);
|
|
52
|
+
if (!existsSync(dir)) return [];
|
|
53
|
+
const out = [];
|
|
54
|
+
for (const name of readdirSync(dir)) {
|
|
55
|
+
if (!name.endsWith('.json')) continue;
|
|
56
|
+
try {
|
|
57
|
+
out.push(JSON.parse(readFileSync(path.join(dir, name), 'utf8')));
|
|
58
|
+
} catch {
|
|
59
|
+
/* a corrupt run file is skipped, not fatal to evidence collection */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function resolveWorkflowType(run) {
|
|
66
|
+
return run?.request?.workflowType || run?.plan?.suggestedWorkflowType || null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// run.executionState (LMCP-F4) already reconciles every task's signal into
|
|
70
|
+
// one of executed | degraded-executed | prepared | failed; only a legacy run
|
|
71
|
+
// with no executionState falls back to the coarser run.status mapping.
|
|
72
|
+
|
|
73
|
+
const STATUS_TO_OUTCOME = {
|
|
74
|
+
'completed': 'executed',
|
|
75
|
+
'degraded': 'degraded-executed',
|
|
76
|
+
'completed-prepare-only': 'prepared',
|
|
77
|
+
'completed-with-failures': 'failed',
|
|
78
|
+
'cancelled': 'failed',
|
|
79
|
+
'error': 'failed',
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function resolveOutcome(run) {
|
|
83
|
+
if (run.executionState) return run.executionState;
|
|
84
|
+
return STATUS_TO_OUTCOME[run.status] || null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveTimestamp(run) {
|
|
88
|
+
return run.updatedAt || run.createdAt || null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Map every persisted run onto a runtime-evidence node + evidenced_by edge to
|
|
93
|
+
* its workflow node, when the run resolves to a known workflow type and has
|
|
94
|
+
* reached a terminal (non-planned, non-running) state.
|
|
95
|
+
*
|
|
96
|
+
* @param {object} opts
|
|
97
|
+
* @param {string} opts.rootDir
|
|
98
|
+
* @returns {{ nodes: object[], edges: object[], skippedRunIds: string[] }}
|
|
99
|
+
*/
|
|
100
|
+
export function buildRuntimeEvidence({ rootDir }) {
|
|
101
|
+
const runs = readRunRecords(rootDir);
|
|
102
|
+
const nodes = [];
|
|
103
|
+
const edges = [];
|
|
104
|
+
const skipped = [];
|
|
105
|
+
|
|
106
|
+
for (const run of runs) {
|
|
107
|
+
if (!run?.runId) continue;
|
|
108
|
+
if (run.status === 'planned' || run.status === 'running') { skipped.push(run.runId); continue; }
|
|
109
|
+
const workflowType = resolveWorkflowType(run);
|
|
110
|
+
if (!workflowType) { skipped.push(run.runId); continue; }
|
|
111
|
+
|
|
112
|
+
const timestamp = resolveTimestamp(run);
|
|
113
|
+
const outcome = resolveOutcome(run);
|
|
114
|
+
const evidenceId = nodeId('runtime-evidence', run.runId);
|
|
115
|
+
const workflowNodeId = nodeId('workflow', workflowType);
|
|
116
|
+
|
|
117
|
+
nodes.push({
|
|
118
|
+
id: evidenceId,
|
|
119
|
+
type: 'runtime-evidence',
|
|
120
|
+
name: run.runId,
|
|
121
|
+
attrs: {
|
|
122
|
+
runId: run.runId,
|
|
123
|
+
workflowType,
|
|
124
|
+
timestamp,
|
|
125
|
+
outcome,
|
|
126
|
+
status: run.status,
|
|
127
|
+
taskCount: Array.isArray(run.tasks) ? run.tasks.length : 0,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
edges.push({
|
|
131
|
+
from: evidenceId,
|
|
132
|
+
to: workflowNodeId,
|
|
133
|
+
rel: 'evidenced_by',
|
|
134
|
+
source: 'runtime-evidence',
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return { nodes, edges, skippedRunIds: skipped };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Reduce per-run evidence nodes to one summary per workflow type: the most
|
|
143
|
+
* recent timestamp wins. Workflow types with zero evidence are present with
|
|
144
|
+
* `lastExecution: null` so a caller can distinguish never-executed from
|
|
145
|
+
* merely-stale without a second pass over the graph.
|
|
146
|
+
*
|
|
147
|
+
* @param {{ nodes: object[] }} evidence — buildRuntimeEvidence() output.
|
|
148
|
+
* @returns {Record<string, { timestamp: string, outcome: string, runId: string }|null>}
|
|
149
|
+
*/
|
|
150
|
+
export function latestEvidenceByWorkflow(evidence) {
|
|
151
|
+
const byWorkflow = {};
|
|
152
|
+
for (const wf of listWorkflowDefs()) {
|
|
153
|
+
byWorkflow[wf.type] = null;
|
|
154
|
+
}
|
|
155
|
+
for (const n of evidence.nodes) {
|
|
156
|
+
if (n.type !== 'runtime-evidence') continue;
|
|
157
|
+
const { workflowType, timestamp, outcome, runId } = n.attrs || {};
|
|
158
|
+
if (!workflowType || !timestamp) continue;
|
|
159
|
+
const prev = byWorkflow[workflowType];
|
|
160
|
+
if (!prev || String(timestamp) > String(prev.timestamp)) {
|
|
161
|
+
byWorkflow[workflowType] = { timestamp, outcome, runId };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return byWorkflow;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Convenience: read runs directly off disk and reduce to per-workflow last
|
|
169
|
+
* execution in one call, for callers (CLI, staleness) that don't need the
|
|
170
|
+
* raw node/edge lists.
|
|
171
|
+
*
|
|
172
|
+
* @param {string} rootDir
|
|
173
|
+
* @returns {Record<string, { timestamp: string, outcome: string, runId: string }|null>}
|
|
174
|
+
*/
|
|
175
|
+
export function computeLastExecutionByWorkflow(rootDir) {
|
|
176
|
+
return latestEvidenceByWorkflow(buildRuntimeEvidence({ rootDir }));
|
|
177
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/graph/security-coverage.mjs — wire the adversarial/security test corpus
|
|
3
|
+
* into the living graph and measure OWASP GenAI coverage (LMCP-N8).
|
|
4
|
+
*
|
|
5
|
+
* A security test disconnected from the workflows it protects leaves coverage
|
|
6
|
+
* unmeasurable. This module makes the linkage a first-class graph fact: a test
|
|
7
|
+
* annotated `@owasp LLM01` (and optionally
|
|
8
|
+
* `@secures <workflow-id>`) becomes a test node carrying its OWASP categories,
|
|
9
|
+
* plus a `test --secures--> workflow` edge for each protected workflow. The
|
|
10
|
+
* coverage queries then read that structure back out of the graph — the matrix
|
|
11
|
+
* and gap list are generated from the graph, never a hand-maintained sidecar.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { nodeId, loadGraph, nodesByType, dependentsOf } from './store.mjs';
|
|
15
|
+
import { extractSecurityTestEdges } from '../test-corpus-inventory.mjs';
|
|
16
|
+
import { loadEmbedCapabilities } from '../embed/capability-loader.mjs';
|
|
17
|
+
|
|
18
|
+
/** OWASP GenAI (LLM) Top 10, 2025 edition — the coverage rubric. */
|
|
19
|
+
export const OWASP_GENAI_TOP10 = Object.freeze([
|
|
20
|
+
{ id: 'LLM01', name: 'Prompt Injection' },
|
|
21
|
+
{ id: 'LLM02', name: 'Sensitive Information Disclosure' },
|
|
22
|
+
{ id: 'LLM03', name: 'Supply Chain' },
|
|
23
|
+
{ id: 'LLM04', name: 'Data and Model Poisoning' },
|
|
24
|
+
{ id: 'LLM05', name: 'Improper Output Handling' },
|
|
25
|
+
{ id: 'LLM06', name: 'Excessive Agency' },
|
|
26
|
+
{ id: 'LLM07', name: 'System Prompt Leakage' },
|
|
27
|
+
{ id: 'LLM08', name: 'Vector and Embedding Weaknesses' },
|
|
28
|
+
{ id: 'LLM09', name: 'Misinformation' },
|
|
29
|
+
{ id: 'LLM10', name: 'Unbounded Consumption' },
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Seed security-test nodes + `secures` edges from `@owasp`/`@secures` tags.
|
|
34
|
+
*
|
|
35
|
+
* @param {{ rootDir: string }} opts
|
|
36
|
+
* @returns {{ nodes: object[], edges: object[] }}
|
|
37
|
+
*/
|
|
38
|
+
export function buildFromSecurity({ rootDir }) {
|
|
39
|
+
const nodes = [];
|
|
40
|
+
const edges = [];
|
|
41
|
+
|
|
42
|
+
// A `@secures <id>` may name an executable workflow or an embed preset; the
|
|
43
|
+
// edge must point at whichever node actually exists, so resolve embed ids
|
|
44
|
+
// (which live as `embed:<id>`, not `workflow:<id>`) up front.
|
|
45
|
+
const embedIds = new Set(loadEmbedCapabilities({ rootDir }).capabilities.map((m) => m.id).filter(Boolean));
|
|
46
|
+
|
|
47
|
+
for (const { testPath, owasp, secures } of extractSecurityTestEdges({ rootDir })) {
|
|
48
|
+
const testId = nodeId('test', testPath);
|
|
49
|
+
nodes.push({ id: testId, type: 'test', name: testPath, attrs: { path: testPath, exists: true, owasp } });
|
|
50
|
+
for (const securedId of secures) {
|
|
51
|
+
const to = embedIds.has(securedId) ? nodeId('embed', securedId) : nodeId('workflow', securedId);
|
|
52
|
+
edges.push({ from: testId, to, rel: 'secures', source: 'corpus-annotation' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { nodes, edges };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Every test node carrying at least one OWASP category, from the live graph. */
|
|
60
|
+
function securityTestNodes(graph) {
|
|
61
|
+
return nodesByType(graph, 'test').filter((n) => Array.isArray(n.attrs?.owasp) && n.attrs.owasp.length > 0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* OWASP coverage matrix generated from the graph: all 10 categories, each with
|
|
66
|
+
* the count and paths of the tests tagged for it (0 for uncovered categories).
|
|
67
|
+
*
|
|
68
|
+
* @param {string} rootDir
|
|
69
|
+
* @returns {{ graphPresent: boolean, categories: Array<{ id, name, testCount, tests: string[] }>, uncovered: string[] }}
|
|
70
|
+
*/
|
|
71
|
+
export function buildOwaspMatrix(rootDir) {
|
|
72
|
+
const graph = loadGraph(rootDir);
|
|
73
|
+
if (!graph.exists) return { graphPresent: false, categories: [], uncovered: [] };
|
|
74
|
+
|
|
75
|
+
const byCategory = new Map(OWASP_GENAI_TOP10.map((c) => [c.id, []]));
|
|
76
|
+
for (const test of securityTestNodes(graph)) {
|
|
77
|
+
for (const cat of test.attrs.owasp) {
|
|
78
|
+
if (byCategory.has(cat)) byCategory.get(cat).push(test.attrs.path ?? test.name);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const categories = OWASP_GENAI_TOP10.map(({ id, name }) => {
|
|
83
|
+
const tests = [...byCategory.get(id)].sort();
|
|
84
|
+
return { id, name, testCount: tests.length, tests };
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
graphPresent: true,
|
|
89
|
+
categories,
|
|
90
|
+
uncovered: categories.filter((c) => c.testCount === 0).map((c) => c.id),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Executable workflows and embed presets with zero inbound `secures` edges —
|
|
96
|
+
* the security-coverage gap list. Embed presets are included because they are
|
|
97
|
+
* the highest-agency units (external reads + write proposals).
|
|
98
|
+
*
|
|
99
|
+
* @param {string} rootDir
|
|
100
|
+
* @returns {{ graphPresent: boolean, workflows: string[], covered: string[] }}
|
|
101
|
+
*/
|
|
102
|
+
export function findWorkflowsMissingSecurity(rootDir) {
|
|
103
|
+
const graph = loadGraph(rootDir);
|
|
104
|
+
if (!graph.exists) return { graphPresent: false, workflows: [], covered: [] };
|
|
105
|
+
|
|
106
|
+
const missing = [];
|
|
107
|
+
const covered = [];
|
|
108
|
+
for (const node of [...nodesByType(graph, 'workflow'), ...nodesByType(graph, 'embed')]) {
|
|
109
|
+
if (dependentsOf(graph, node.id, 'secures').length === 0) missing.push(node.id);
|
|
110
|
+
else covered.push(node.id);
|
|
111
|
+
}
|
|
112
|
+
return { graphPresent: true, workflows: missing.sort(), covered: covered.sort() };
|
|
113
|
+
}
|