@geraldmaron/construct 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +36 -0
- package/README.md +41 -7
- package/bin/construct +1029 -65
- package/bin/construct-postinstall.mjs +27 -2
- package/config/tag-vocabulary.json +264 -0
- package/examples/distribution/sources/deck-one-pager.md +1 -1
- package/lib/acp/server.mjs +21 -7
- package/lib/adapters-sync.mjs +2 -1
- package/lib/audit-trail.mjs +185 -2
- package/lib/beads/auto-close.mjs +2 -1
- package/lib/beads/drift.mjs +2 -1
- package/lib/bridges/copilot-proxy.mjs +20 -5
- package/lib/certification/skill-inventory.mjs +10 -1
- package/lib/cli/approvals.mjs +147 -0
- package/lib/cli-commands.mjs +105 -14
- package/lib/comment-lint.mjs +127 -8
- package/lib/config/schema.mjs +7 -30
- package/lib/config/source-target-registry.mjs +70 -0
- package/lib/config/source-targets.mjs +179 -205
- package/lib/contracts/coverage.mjs +77 -0
- package/lib/contracts/validate.mjs +102 -13
- package/lib/contracts/violation-log.mjs +50 -4
- package/lib/db/migrate.mjs +69 -0
- package/lib/db/migrations/001_orchestration_runs.sql +9 -0
- package/lib/db/migrations/002_queue_provider.sql +50 -0
- package/lib/db/migrations/003_worker_registry.sql +17 -0
- package/lib/db/migrations/004_trace_events.sql +16 -0
- package/lib/db/migrations/005_shared_memory.sql +15 -0
- package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
- package/lib/decisions/enforced-baseline.json +0 -2
- package/lib/decisions/registry.mjs +3 -2
- package/lib/deployment/parity-contract.mjs +1 -1
- package/lib/deployment-mode.mjs +78 -2
- package/lib/diagram-export.mjs +10 -1
- package/lib/distill.mjs +5 -2
- package/lib/docs-verify.mjs +2 -1
- package/lib/doctor/cli.mjs +1 -0
- package/lib/doctor/command-on-path.mjs +24 -0
- package/lib/doctor/diagnosis.mjs +109 -0
- package/lib/doctor/embedding-health.mjs +50 -0
- package/lib/doctor/engine-health.mjs +93 -0
- package/lib/doctor/graph-validate.mjs +47 -0
- package/lib/doctor/index.mjs +23 -4
- package/lib/doctor/sidecar-providers.mjs +56 -0
- package/lib/doctor/watchers/consistency.mjs +93 -1
- package/lib/doctor/watchers/cx-budget.mjs +1 -1
- package/lib/doctor/watchers/graph-staleness.mjs +1 -1
- package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
- package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
- package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
- package/lib/doctor/watchers/provider-breaker.mjs +78 -0
- package/lib/document-export.mjs +52 -27
- package/lib/document-extract/docling-client.mjs +9 -5
- package/lib/document-extract/docling-sidecar.py +30 -0
- package/lib/document-extract.mjs +1 -1
- package/lib/document-ingest.mjs +9 -0
- package/lib/embed/approval-queue.mjs +184 -88
- package/lib/embed/authority-guard.mjs +65 -2
- package/lib/embed/capability-jobs.mjs +365 -0
- package/lib/embed/capability-lifecycle.mjs +219 -0
- package/lib/embed/capability-loader.mjs +303 -0
- package/lib/embed/capability-runtime.mjs +58 -0
- package/lib/embed/cli.mjs +185 -8
- package/lib/embed/config.mjs +4 -0
- package/lib/embed/daemon.mjs +125 -6
- package/lib/embed/demand-fetch.mjs +190 -54
- package/lib/embed/inbox.mjs +12 -10
- package/lib/embed/presets/ops-triage.mjs +236 -0
- package/lib/embed/presets/pm-feedback.mjs +341 -0
- package/lib/embed/presets/tpm.mjs +401 -0
- package/lib/embed/providers/jira.mjs +72 -1
- package/lib/embed/providers/registry.mjs +140 -33
- package/lib/embed/worker.mjs +5 -0
- package/lib/embedded-contract/capability.mjs +4 -0
- package/lib/embedded-contract/execution.mjs +1 -1
- package/lib/embedded-contract/model-resolve.mjs +53 -3
- package/lib/embedded-contract/workflow-defs.mjs +48 -73
- package/lib/embedded-contract/workflow-invoke.mjs +144 -4
- package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
- package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
- package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
- package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
- package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
- package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
- package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
- package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
- package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
- package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
- package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
- package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
- package/lib/env-config.mjs +60 -11
- package/lib/export-validate.mjs +34 -2
- package/lib/extensions/index.mjs +27 -0
- package/lib/extensions/loader.mjs +120 -0
- package/lib/extensions/manifest-schema.mjs +141 -0
- package/lib/extensions/manifests/anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
- package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
- package/lib/extensions/manifests/directory.manifest.json +12 -0
- package/lib/extensions/manifests/docling.manifest.json +37 -0
- package/lib/extensions/manifests/echo.manifest.json +8 -0
- package/lib/extensions/manifests/feedback.manifest.json +12 -0
- package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
- package/lib/extensions/manifests/github.manifest.json +52 -0
- package/lib/extensions/manifests/linear.manifest.json +50 -0
- package/lib/extensions/manifests/local.manifest.json +12 -0
- package/lib/extensions/manifests/ollama.manifest.json +12 -0
- package/lib/extensions/manifests/openai.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
- package/lib/extensions/manifests/openrouter.manifest.json +12 -0
- package/lib/extensions/manifests/postgres.manifest.json +12 -0
- package/lib/extensions/manifests/salesforce.manifest.json +12 -0
- package/lib/extensions/manifests/slack.manifest.json +58 -0
- package/lib/extensions/manifests/whisper.manifest.json +36 -0
- package/lib/extensions/validate.mjs +175 -0
- package/lib/features.mjs +13 -9
- package/lib/flows/checkpoint.mjs +184 -0
- package/lib/flows/constants.mjs +27 -0
- package/lib/flows/define.mjs +146 -0
- package/lib/flows/engine.mjs +249 -0
- package/lib/flows/errors.mjs +19 -0
- package/lib/flows/index.mjs +27 -0
- package/lib/flows/joins.mjs +18 -0
- package/lib/flows/schema.mjs +90 -0
- package/lib/flows/state.mjs +31 -0
- package/lib/frameworks/loader.mjs +99 -0
- package/lib/frameworks/schema.mjs +157 -0
- package/lib/graph/build-from-corpus.mjs +67 -0
- package/lib/graph/build-from-embed.mjs +82 -0
- package/lib/graph/build-from-registry.mjs +164 -3
- package/lib/graph/cli.mjs +456 -12
- package/lib/graph/gap-queries.mjs +156 -0
- package/lib/graph/gaps.mjs +41 -0
- package/lib/graph/impacted.mjs +129 -0
- package/lib/graph/runtime-evidence.mjs +177 -0
- package/lib/graph/security-coverage.mjs +113 -0
- package/lib/graph/staleness.mjs +241 -10
- package/lib/graph/store.mjs +24 -7
- package/lib/graph/validate.mjs +161 -0
- package/lib/headhunt.mjs +9 -8
- package/lib/health-check.mjs +15 -7
- package/lib/hook-health.mjs +1 -1
- package/lib/hooks/agent-tracker.mjs +10 -4
- package/lib/hooks/edit-guard.mjs +3 -3
- package/lib/hooks/graph-impact-advisory.mjs +2 -2
- package/lib/hooks/guard-bash.mjs +41 -15
- package/lib/hooks/mcp-health-check.mjs +11 -4
- package/lib/hooks/model-fallback.mjs +50 -6
- package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
- package/lib/hooks/pre-compact.mjs +181 -173
- package/lib/hooks/rule-verifier.mjs +2 -1
- package/lib/hooks/session-reflect.mjs +2 -1
- package/lib/hooks/session-start.mjs +3 -3
- package/lib/host/readiness.mjs +109 -0
- package/lib/host-capabilities.mjs +13 -2
- package/lib/host-disposition.mjs +29 -8
- package/lib/identity.mjs +92 -0
- package/lib/ingest/degraded-extract.mjs +96 -0
- package/lib/ingest/docling-remote.mjs +2 -1
- package/lib/ingest/provider-extract.mjs +7 -19
- package/lib/ingest/sidecar-providers.mjs +196 -0
- package/lib/ingest-tooling.mjs +11 -5
- package/lib/init-unified.mjs +55 -38
- package/lib/init-update.mjs +2 -1
- package/lib/install/legacy-global-cleanup.mjs +25 -0
- package/lib/install/stage-project.mjs +41 -4
- package/lib/intake/daemon.mjs +99 -8
- package/lib/intake/git-queue.mjs +110 -38
- package/lib/intake/prepare.mjs +2 -0
- package/lib/intake/queue-registry.mjs +50 -0
- package/lib/intake/queue.mjs +133 -17
- package/lib/intake/session-prelude.mjs +57 -8
- package/lib/integrations/intake-integrations.mjs +61 -111
- package/lib/intent-classifier.mjs +43 -5
- package/lib/libreoffice-export.mjs +8 -8
- package/lib/logging/rotate.mjs +5 -4
- package/lib/mcp/broker.mjs +332 -17
- package/lib/mcp/denied-store.mjs +95 -0
- package/lib/mcp/destructive-approval.mjs +57 -0
- package/lib/mcp/destructive-gate.mjs +30 -0
- package/lib/mcp/dispatch-envelope.mjs +199 -0
- package/lib/mcp/memory-bridge.mjs +2 -1
- package/lib/mcp/server.mjs +185 -1268
- package/lib/mcp/tool-definitions-memory.mjs +376 -0
- package/lib/mcp/tool-definitions-project.mjs +271 -0
- package/lib/mcp/tool-definitions-skills.mjs +311 -0
- package/lib/mcp/tool-definitions-workflow.mjs +398 -0
- package/lib/mcp/tool-definitions.mjs +25 -0
- package/lib/mcp/tool-rate-limit.mjs +47 -0
- package/lib/mcp/tool-registry.mjs +107 -0
- package/lib/mcp/tool-safety.mjs +95 -0
- package/lib/mcp/tool-surface-parity.mjs +60 -0
- package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
- package/lib/mcp/tools/orchestration-run.mjs +197 -19
- package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
- package/lib/mcp/tools/project.mjs +25 -8
- package/lib/mcp/tools/provider-write.mjs +187 -0
- package/lib/mcp/tools/scope.mjs +0 -3
- package/lib/mcp/tools/skills.mjs +1 -1
- package/lib/mcp/tools/storage.mjs +2 -2
- package/lib/mcp/tools/web-search-governance.mjs +96 -0
- package/lib/mcp/tools/web-search.mjs +5 -76
- package/lib/mcp/transport/auth.mjs +238 -0
- package/lib/mcp/transport/http.mjs +136 -0
- package/lib/mcp/transport/mode.mjs +31 -0
- package/lib/mcp/transport/stdio.mjs +22 -0
- package/lib/mcp-catalog.json +4 -4
- package/lib/mcp-manager.mjs +34 -23
- package/lib/mcp-platform-config.mjs +53 -20
- package/lib/mode-capabilities.mjs +109 -0
- package/lib/model-router.mjs +42 -9
- package/lib/models/catalog.mjs +40 -1
- package/lib/net-guard.mjs +247 -0
- package/lib/observation-store.mjs +6 -2
- package/lib/ollama/provision-context.mjs +2 -1
- package/lib/opencode-config.mjs +22 -3
- package/lib/opencode-runtime-plugin.mjs +120 -2
- package/lib/oracle/actions.mjs +204 -10
- package/lib/oracle/cli.mjs +24 -3
- package/lib/oracle/daemon-entry.mjs +6 -0
- package/lib/oracle/dispatch.mjs +2 -1
- package/lib/oracle/execute.mjs +2 -2
- package/lib/oracle/gaps.mjs +3 -3
- package/lib/oracle/heartbeat.mjs +53 -0
- package/lib/oracle/read-model.mjs +69 -13
- package/lib/oracle/reconcile.mjs +3 -46
- package/lib/oracle/routing.mjs +37 -31
- package/lib/oracle/synthesize.mjs +1 -1
- package/lib/orchestration/classification.mjs +434 -0
- package/lib/orchestration/delegation-flow.mjs +132 -0
- package/lib/orchestration/flow-selection.mjs +418 -0
- package/lib/orchestration/gates.mjs +244 -0
- package/lib/orchestration/host-sampling.mjs +121 -0
- package/lib/orchestration/policy-constants.mjs +48 -0
- package/lib/orchestration/provider-outcome.mjs +197 -0
- package/lib/orchestration/readiness.mjs +114 -13
- package/lib/orchestration/run-store-postgres.mjs +32 -26
- package/lib/orchestration/run-store-sqlite.mjs +8 -14
- package/lib/orchestration/run-store.mjs +25 -12
- package/lib/orchestration/runtime.mjs +508 -62
- package/lib/orchestration/store.mjs +87 -12
- package/lib/orchestration/trace-store.mjs +125 -0
- package/lib/orchestration/web-capability.mjs +60 -0
- package/lib/orchestration/worker-runtime.mjs +162 -0
- package/lib/orchestration/worker.mjs +763 -80
- package/lib/orchestration-policy.mjs +67 -1111
- package/lib/output-quality.mjs +61 -2
- package/lib/packs/cli.mjs +144 -0
- package/lib/packs/core-pack.mjs +112 -0
- package/lib/packs/enablement.mjs +187 -0
- package/lib/packs/index.mjs +23 -0
- package/lib/packs/loader.mjs +176 -0
- package/lib/packs/manifest-schema.mjs +58 -0
- package/lib/packs/prompts.mjs +120 -0
- package/lib/packs/validate.mjs +208 -0
- package/lib/path-policy.mjs +56 -0
- package/lib/plugin-registry.mjs +4 -5
- package/lib/policy/audit-gate.mjs +42 -0
- package/lib/policy/consumption-budget.mjs +149 -0
- package/lib/policy/engine.mjs +81 -6
- package/lib/policy/role-authority.mjs +89 -0
- package/lib/prompt-composer.js +19 -3
- package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
- package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
- package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
- package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
- package/lib/providers/contract/adapters/github/index.mjs +38 -3
- package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
- package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
- package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
- package/lib/providers/contract/adapters/jira/manifest.json +17 -0
- package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
- package/lib/providers/contract.mjs +207 -0
- package/lib/providers/credential-bootstrap.mjs +7 -4
- package/lib/providers/credential-sources.mjs +14 -2
- package/lib/providers/directory/index.mjs +261 -0
- package/lib/providers/feedback/index.mjs +392 -0
- package/lib/providers/filter-audit.mjs +68 -0
- package/lib/providers/filter-schema.mjs +54 -0
- package/lib/providers/github/index.mjs +31 -0
- package/lib/providers/instance-config.mjs +215 -0
- package/lib/providers/op-locate.mjs +96 -0
- package/lib/providers/op-run.mjs +64 -9
- package/lib/providers/registry.mjs +26 -3
- package/lib/providers/secret-audit-wiring.mjs +41 -18
- package/lib/providers/secret-resolver.mjs +263 -31
- package/lib/publish-template.mjs +6 -3
- package/lib/publish-tooling.mjs +4 -0
- package/lib/publish.mjs +32 -4
- package/lib/queue/pg-queue.mjs +395 -0
- package/lib/reflect.mjs +2 -1
- package/lib/registry/assemble.mjs +28 -2
- package/lib/registry/catalog.mjs +6 -0
- package/lib/registry/consolidation.mjs +8 -1
- package/lib/registry/custom-scaffold.mjs +200 -0
- package/lib/registry/custom-schema.mjs +137 -0
- package/lib/registry/loader.mjs +15 -4
- package/lib/registry/manifests/format-engines.default.json +15 -0
- package/lib/registry/manifests/surface-map.default.json +46 -0
- package/lib/registry/retired-paths.mjs +1 -0
- package/lib/registry/surface-map.mjs +100 -50
- package/lib/registry/validate.mjs +3 -3
- package/lib/render-visual-check.mjs +240 -0
- package/lib/resources/budget.mjs +40 -17
- package/lib/roles/flavor-bindings.mjs +36 -14
- package/lib/roles/gateway.mjs +15 -8
- package/lib/roots.mjs +107 -0
- package/lib/runtime/uv-bootstrap.mjs +32 -6
- package/lib/runtime/whisper-bootstrap.mjs +11 -3
- package/lib/runtime-env.mjs +5 -2
- package/lib/sandbox.mjs +1 -1
- package/lib/scheduler/index.mjs +8 -20
- package/lib/schema-infer.mjs +31 -2
- package/lib/scopes/lifecycle.mjs +29 -25
- package/lib/scopes/loader.mjs +49 -12
- package/lib/scopes/teams.mjs +1 -1
- package/lib/security/ingest-boundary.mjs +80 -0
- package/lib/security/recall-wrapper.mjs +99 -0
- package/lib/security/trust.mjs +132 -0
- package/lib/service-manager.mjs +93 -24
- package/lib/setup.mjs +41 -23
- package/lib/skills-apply.mjs +4 -2
- package/lib/specialists/postconditions.mjs +2 -2
- package/lib/state-root.mjs +147 -0
- package/lib/status.mjs +597 -6
- package/lib/storage/admin.mjs +84 -7
- package/lib/storage/backend-registry.mjs +73 -0
- package/lib/storage/backend.mjs +63 -9
- package/lib/storage/embeddings-openai.mjs +12 -2
- package/lib/storage/hybrid-query.mjs +11 -3
- package/lib/storage/retrieval-hardening.mjs +384 -0
- package/lib/storage/shared-memory.mjs +158 -0
- package/lib/storage/vector-client.mjs +69 -2
- package/lib/team/health.mjs +72 -0
- package/lib/telemetry/backends/local.mjs +9 -3
- package/lib/telemetry/client.mjs +3 -7
- package/lib/template-registry.mjs +10 -12
- package/lib/tenant/context.mjs +82 -0
- package/lib/tenant/isolation.mjs +129 -0
- package/lib/test-corpus-inventory.mjs +103 -2
- package/lib/uninstall/uninstall.mjs +78 -12
- package/lib/validator.mjs +4 -6
- package/lib/worker/entrypoint.mjs +2 -1
- package/lib/worker/run.mjs +2 -2
- package/lib/worker/trace.mjs +5 -11
- package/lib/workflow-state.mjs +52 -8
- package/lib/workflows/liveness.mjs +203 -0
- package/lib/workflows/loader.mjs +177 -0
- package/lib/workflows/manifest-schema.mjs +71 -0
- package/lib/workflows/surface-parity.mjs +118 -0
- package/lib/workflows/validate.mjs +127 -0
- package/lib/writes/control-plane.mjs +140 -0
- package/lib/writes/envelope.mjs +206 -0
- package/lib/writes/sent-log.mjs +86 -0
- package/lib/writes/write-intent.mjs +109 -0
- package/package.json +20 -7
- package/personas/construct.md +12 -3
- package/registry/agent-manifest.json +117 -0
- package/registry/capabilities.json +1987 -0
- package/rules/common/comments.md +1 -0
- package/schemas/brand-voice.schema.json +24 -0
- package/schemas/capability-registry.schema.json +72 -0
- package/schemas/certification-run.schema.json +130 -0
- package/schemas/demo-recording.schema.json +46 -0
- package/schemas/eval-dataset.schema.json +79 -0
- package/schemas/execution-capability-profile.schema.json +46 -0
- package/schemas/execution-policy.schema.json +114 -0
- package/schemas/improvement-proposal.schema.json +65 -0
- package/schemas/mcp-tool-output.schema.json +61 -0
- package/schemas/platform-capabilities.schema.json +83 -0
- package/schemas/project-config.schema.json +215 -0
- package/schemas/project-demo.schema.json +60 -0
- package/schemas/provider-behavior-matrix.schema.json +91 -0
- package/schemas/scope.schema.json +197 -0
- package/schemas/specialist-trace.schema.json +107 -0
- package/schemas/team.schema.json +99 -0
- package/schemas/unified-registry.schema.json +546 -0
- package/scripts/sync-specialists.mjs +336 -106
- package/skills/docs/adr-workflow.md +1 -1
- package/skills/docs/codebase-research-workflow.md +3 -3
- package/skills/docs/prd-workflow.md +5 -6
- package/skills/docs/runbook-workflow.md +2 -2
- package/skills/docs/user-research-workflow.md +3 -3
- package/skills/operating/fleet-health-routing.md +77 -0
- package/skills/roles/ai-engineer.md +1 -1
- package/skills/roles/architect.md +0 -1
- package/skills/roles/business-strategist.md +1 -1
- package/skills/roles/data-analyst.telemetry.md +1 -1
- package/skills/roles/data-engineer.md +1 -1
- package/skills/roles/data-engineer.pipeline.md +1 -1
- package/skills/roles/data-engineer.vector-retrieval.md +1 -2
- package/skills/roles/data-engineer.warehouse.md +1 -1
- package/skills/roles/designer.accessibility.md +1 -1
- package/skills/roles/designer.md +0 -1
- package/skills/roles/devil-advocate.md +1 -1
- package/skills/roles/docs-keeper.md +1 -1
- package/skills/roles/evaluator.md +1 -1
- package/skills/roles/explorer.md +1 -1
- package/skills/roles/platform-engineer.md +1 -1
- package/skills/roles/qa.ai-eval.md +1 -2
- package/skills/roles/qa.api-contract.md +0 -1
- package/skills/roles/qa.data-pipeline.md +0 -1
- package/skills/roles/qa.md +0 -1
- package/skills/roles/qa.web-ui.md +0 -1
- package/skills/roles/release-manager.md +1 -1
- package/skills/roles/researcher.md +0 -2
- package/skills/roles/reviewer.md +0 -3
- package/skills/roles/security.ai.md +1 -1
- package/skills/roles/security.legal-compliance.md +1 -1
- package/skills/roles/security.md +0 -1
- package/skills/roles/security.privacy.md +0 -1
- package/skills/roles/security.supply-chain.md +1 -1
- package/skills/roles/sre.md +1 -1
- package/skills/roles/test-automation.md +1 -1
- package/skills/roles/trace-reviewer.md +1 -1
- package/skills/roles/ux-researcher.md +1 -1
- package/specialists/artifact-manifest.json +36 -36
- package/specialists/org/contracts/accessibility-to-qa.json +11 -3
- package/specialists/org/contracts/any-to-business-strategist.json +19 -7
- package/specialists/org/contracts/any-to-debugger.json +7 -1
- package/specialists/org/contracts/any-to-designer.json +7 -1
- package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
- package/specialists/org/contracts/any-to-explorer.json +13 -4
- package/specialists/org/contracts/any-to-sre-incident.json +6 -2
- package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
- package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
- package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
- package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
- package/specialists/org/contracts/architect-to-engineer.json +20 -4
- package/specialists/org/contracts/architect-to-evaluator.json +15 -5
- package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
- package/specialists/org/contracts/architect-to-operations.json +17 -4
- package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
- package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
- package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
- package/specialists/org/contracts/engineer-to-qa.json +20 -4
- package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
- package/specialists/org/contracts/explorer-to-engineer.json +11 -3
- package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
- package/specialists/org/contracts/operations-to-user.json +53 -0
- package/specialists/org/contracts/operations-triage-output.json +53 -0
- package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
- package/specialists/org/contracts/product-manager-to-architect.json +30 -10
- package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
- package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
- package/specialists/org/contracts/qa-to-release-manager.json +11 -3
- package/specialists/org/contracts/researcher-to-architect.json +10 -2
- package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
- package/specialists/org/contracts/reviewer-to-security.json +17 -3
- package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
- package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
- package/specialists/org/contracts/user-to-construct.json +11 -4
- package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
- package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
- package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
- package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
- package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
- package/specialists/org/groups/engineering-group.json +2 -6
- package/specialists/org/groups/governance-group.json +2 -3
- package/specialists/org/groups/operations-group.json +5 -8
- package/specialists/org/groups/product-group.json +4 -8
- package/specialists/org/groups/quality-group.json +3 -7
- package/specialists/org/groups/strategy-group.json +6 -9
- package/specialists/org/models.json.example +8 -0
- package/specialists/org/scopes/creative.json +6 -1
- package/specialists/org/scopes/operations.json +7 -1
- package/specialists/org/scopes/research.json +6 -1
- package/specialists/org/scopes/rnd.json +7 -1
- package/specialists/org/specialists/cx-architect.json +27 -8
- package/specialists/org/specialists/cx-designer.json +22 -8
- package/specialists/org/specialists/cx-engineer.json +71 -7
- package/specialists/org/specialists/cx-operations.json +89 -15
- package/specialists/org/specialists/cx-orchestrator.json +16 -4
- package/specialists/org/specialists/cx-product-manager.json +28 -9
- package/specialists/org/specialists/cx-qa.json +16 -6
- package/specialists/org/specialists/cx-researcher.json +41 -7
- package/specialists/org/specialists/cx-reviewer.json +61 -11
- package/specialists/org/specialists/cx-security.json +30 -10
- package/specialists/org/teams/design-team.json +3 -4
- package/specialists/org/teams/engineering-team.json +3 -10
- package/specialists/org/teams/governance-team.json +3 -5
- package/specialists/org/teams/operations-team.json +6 -12
- package/specialists/org/teams/product-management-team.json +2 -3
- package/specialists/org/teams/quality-team.json +4 -12
- package/specialists/org/teams/research-team.json +3 -3
- package/specialists/org/teams/strategy-team.json +7 -14
- package/specialists/prompts/cx-architect.md +20 -1
- package/specialists/prompts/cx-data-analyst.md +1 -1
- package/specialists/prompts/cx-designer.md +12 -4
- package/specialists/prompts/cx-engineer.md +15 -1
- package/specialists/prompts/cx-operations.md +14 -2
- package/specialists/prompts/cx-orchestrator.md +9 -5
- package/specialists/prompts/cx-product-manager.md +5 -1
- package/specialists/prompts/cx-qa.md +6 -2
- package/specialists/prompts/cx-researcher.md +27 -31
- package/specialists/prompts/cx-reviewer.md +19 -1
- package/specialists/prompts/cx-security.md +5 -1
- package/templates/distribution/construct-brand.typ +123 -59
- package/templates/distribution/construct-decision.typ +6 -3
- package/templates/distribution/construct-pdf.typ +11 -4
- package/templates/distribution/construct-prd.typ +6 -3
- package/templates/distribution/construct-research.typ +7 -4
- package/vendor/pandoc-ext/README.md +3 -0
- package/vendor/pandoc-ext/diagram.lua +687 -0
- package/lib/providers/contract/adapters/git/index.mjs +0 -115
- package/lib/providers/contract/adapters/slack/index.mjs +0 -175
- package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
- package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
- package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
- package/specialists/org/contracts/designer-to-accessibility.json +0 -36
- package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
- package/specialists/org/contracts/qa-to-test-automation.json +0 -42
- package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
- package/specialists/org/contracts/sre-to-release-manager.json +0 -36
- package/specialists/org/specialists/cx-accessibility.json +0 -69
- package/specialists/org/specialists/cx-ai-engineer.json +0 -75
- package/specialists/org/specialists/cx-business-strategist.json +0 -72
- package/specialists/org/specialists/cx-data-engineer.json +0 -71
- package/specialists/org/specialists/cx-devil-advocate.json +0 -71
- package/specialists/org/specialists/cx-docs-keeper.json +0 -82
- package/specialists/org/specialists/cx-evaluator.json +0 -69
- package/specialists/org/specialists/cx-explorer.json +0 -69
- package/specialists/org/specialists/cx-legal-compliance.json +0 -74
- package/specialists/org/specialists/cx-oracle.json +0 -46
- package/specialists/org/specialists/cx-platform-engineer.json +0 -80
- package/specialists/org/specialists/cx-rd-lead.json +0 -73
- package/specialists/org/specialists/cx-release-manager.json +0 -77
- package/specialists/org/specialists/cx-sre.json +0 -94
- package/specialists/org/specialists/cx-test-automation.json +0 -69
- package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
- package/specialists/org/specialists/cx-ux-researcher.json +0 -68
- package/specialists/org/teams/accessibility-team.json +0 -39
- package/specialists/org/teams/ux-research-team.json +0 -39
- package/specialists/prompts/cx-accessibility.md +0 -55
- package/specialists/prompts/cx-ai-engineer.md +0 -124
- package/specialists/prompts/cx-business-strategist.md +0 -58
- package/specialists/prompts/cx-data-engineer.md +0 -55
- package/specialists/prompts/cx-devil-advocate.md +0 -59
- package/specialists/prompts/cx-docs-keeper.md +0 -164
- package/specialists/prompts/cx-evaluator.md +0 -49
- package/specialists/prompts/cx-explorer.md +0 -71
- package/specialists/prompts/cx-legal-compliance.md +0 -58
- package/specialists/prompts/cx-oracle.md +0 -98
- package/specialists/prompts/cx-platform-engineer.md +0 -97
- package/specialists/prompts/cx-rd-lead.md +0 -59
- package/specialists/prompts/cx-release-manager.md +0 -54
- package/specialists/prompts/cx-sre.md +0 -111
- package/specialists/prompts/cx-test-automation.md +0 -55
- package/specialists/prompts/cx-trace-reviewer.md +0 -101
- package/specialists/prompts/cx-ux-researcher.md +0 -53
package/lib/validator.mjs
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* before they mutate persistent state.
|
|
8
8
|
*/
|
|
9
9
|
import { loadRegistry } from './registry/loader.mjs';
|
|
10
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
11
10
|
import { existsSync, readFileSync } from 'node:fs';
|
|
12
|
-
import { join
|
|
11
|
+
import { join } from 'node:path';
|
|
12
|
+
import { packageRoot, isMainModule } from './roots.mjs';
|
|
13
13
|
|
|
14
14
|
const VALID_TIERS = new Set(['reasoning', 'standard', 'fast']);
|
|
15
15
|
const VALID_REASONING_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh']);
|
|
@@ -54,7 +54,7 @@ function validateStringArray(errors, value, label, validValues = null) {
|
|
|
54
54
|
|
|
55
55
|
export function validateRegistry(reg, options = {}) {
|
|
56
56
|
const errors = [];
|
|
57
|
-
const rootDir = options.rootDir ??
|
|
57
|
+
const rootDir = options.rootDir ?? packageRoot;
|
|
58
58
|
|
|
59
59
|
// Handle unified registry format (v2) - convert specialists from object to array
|
|
60
60
|
// and extract orchestrator from specialists
|
|
@@ -252,9 +252,7 @@ export function validateRegistry(reg, options = {}) {
|
|
|
252
252
|
return { valid: errors.length === 0, errors, summary };
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
255
|
+
if (isMainModule(import.meta.url)) {
|
|
258
256
|
const reg = loadRegistry();
|
|
259
257
|
const result = validateRegistry(reg);
|
|
260
258
|
if (result.valid) {
|
|
@@ -29,6 +29,7 @@ import { FilesystemTaskGraphStore } from '../task-graph/store.mjs';
|
|
|
29
29
|
import { runJob } from './run.mjs';
|
|
30
30
|
import { evidenceFromJobResult, recordEvidence, blockedPacket } from './evidence.mjs';
|
|
31
31
|
import { emitTraceEvent, newTraceId } from './trace.mjs';
|
|
32
|
+
import { isMainModule } from '../roots.mjs';
|
|
32
33
|
|
|
33
34
|
const DEFAULT_POLL_INTERVAL_MS = 2_000;
|
|
34
35
|
const DEFAULT_IDLE_TIMEOUT_SECONDS = 300;
|
|
@@ -211,7 +212,7 @@ export async function runWorkerLoop({
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
// CLI entrypoint when invoked directly (the Dockerfile.worker CMD).
|
|
214
|
-
const invokedDirectly = import.meta.url
|
|
215
|
+
const invokedDirectly = isMainModule(import.meta.url);
|
|
215
216
|
if (invokedDirectly) {
|
|
216
217
|
const args = parseArgs(process.argv);
|
|
217
218
|
const workspace = args.workspace || process.env.CONSTRUCT_WORKSPACE || process.cwd();
|
package/lib/worker/run.mjs
CHANGED
|
@@ -28,12 +28,12 @@ import path from 'node:path';
|
|
|
28
28
|
|
|
29
29
|
import { emitTraceEvent, newTraceId, newSpanId } from './trace.mjs';
|
|
30
30
|
import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
|
|
31
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
31
32
|
|
|
32
33
|
const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
33
|
-
const ARTIFACTS_SUBDIR = '.cx/runtime/worker';
|
|
34
34
|
|
|
35
35
|
function artifactsDir(rootDir) {
|
|
36
|
-
return
|
|
36
|
+
return resolveStateDir(rootDir, 'runtime', 'worker');
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function buildRestrictedEnv(allowedKeys = []) {
|
package/lib/worker/trace.mjs
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
* job → evidence record together via traceId + spanId. Events land in two
|
|
6
6
|
* places concurrently:
|
|
7
7
|
*
|
|
8
|
-
* 1.
|
|
9
|
-
*
|
|
8
|
+
* 1. `<stateRoot>/traces/<YYYY-MM-DD>.jsonl` (ADR-0066: machine-scoped, not
|
|
9
|
+
* project-local) — append-only local JSONL. Always on, no credentials
|
|
10
|
+
* required. Tail live, archive, or replay.
|
|
10
11
|
* 2. Optional remote export — selected by CONSTRUCT_TRACE_BACKEND. Each
|
|
11
12
|
* unique traceId triggers one trace-create plus an event-create per
|
|
12
13
|
* emitted event. Failures never throw because observability must not
|
|
@@ -26,16 +27,13 @@
|
|
|
26
27
|
* memory.written — durable memory record persisted
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
30
30
|
import { randomBytes } from 'node:crypto';
|
|
31
31
|
import path from 'node:path';
|
|
32
32
|
|
|
33
33
|
import { createTelemetryClient } from '../telemetry/client.mjs';
|
|
34
|
-
import { ensureCxDir } from '../project-init-shared.mjs';
|
|
35
34
|
import { appendWithRotationSync } from '../logging/rotate.mjs';
|
|
36
35
|
import { ensureDiskWrite, warnBudgetOnce } from '../resources/budget.mjs';
|
|
37
|
-
|
|
38
|
-
const TRACE_SUBDIR = '.cx/traces';
|
|
36
|
+
import { resolveStateDir } from '../state-root.mjs';
|
|
39
37
|
|
|
40
38
|
// GitHub rejects single files > 100 MB. Keep every trace shard strictly
|
|
41
39
|
// under 100 MB so an accidental commit + push doesn't brick the repo. Cap
|
|
@@ -69,7 +67,7 @@ export const TRACE_EVENT_TYPES = [
|
|
|
69
67
|
];
|
|
70
68
|
|
|
71
69
|
export function traceDir(rootDir) {
|
|
72
|
-
return
|
|
70
|
+
return resolveStateDir(rootDir, 'traces');
|
|
73
71
|
}
|
|
74
72
|
|
|
75
73
|
function todayShard() {
|
|
@@ -166,10 +164,6 @@ export function emitTraceEvent({
|
|
|
166
164
|
}
|
|
167
165
|
|
|
168
166
|
const dir = traceDir(rootDir);
|
|
169
|
-
if (!existsSync(dir)) {
|
|
170
|
-
ensureCxDir(rootDir);
|
|
171
|
-
mkdirSync(dir, { recursive: true });
|
|
172
|
-
}
|
|
173
167
|
|
|
174
168
|
const event = {
|
|
175
169
|
traceId: traceId || newTraceId(),
|
package/lib/workflow-state.mjs
CHANGED
|
@@ -11,6 +11,7 @@ import fs from "node:fs";
|
|
|
11
11
|
import path from "node:path";
|
|
12
12
|
import { execSync } from "node:child_process";
|
|
13
13
|
import { routeRequest } from "./orchestration-policy.mjs";
|
|
14
|
+
import { isMainModule } from './roots.mjs';
|
|
14
15
|
|
|
15
16
|
const PHASES = ["research", "plan", "implement", "validate", "operate"];
|
|
16
17
|
const VALID_STATUS = new Set(["todo", "in-progress", "blocked", "blocked_needs_user", "blocked_needs_executive", "done", "skipped"]);
|
|
@@ -55,6 +56,42 @@ function now() {
|
|
|
55
56
|
return new Date().toISOString();
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
// rules/common/no-fabrication.md requires evidence a reader can re-verify.
|
|
60
|
+
// A bare non-empty string ("did stuff") tells nobody how to check it, so the
|
|
61
|
+
// done-gate below must look past mere presence to a re-verifiable shape: a
|
|
62
|
+
// runnable command, a file path, a URL, a test reference, or a named
|
|
63
|
+
// specialist's structured verdict (the "cx-reviewer: APPROVED" / "cx-qa: 42
|
|
64
|
+
// tests passing" attestation format this workflow already standardizes on —
|
|
65
|
+
// see tests/concierge-routing.test.mjs).
|
|
66
|
+
|
|
67
|
+
const VERIFICATION_URL_RE = /https?:\/\/\S+/i;
|
|
68
|
+
const VERIFICATION_FILE_RE = /[\w.-]*\/[\w./-]+\.\w+|\b[\w-]+\.(mjs|cjs|js|ts|tsx|jsx|py|rb|go|java|json|ya?ml|sh|md)\b/i;
|
|
69
|
+
const VERIFICATION_COMMAND_RE = /\b(npm|yarn|pnpm|npx|node|python3?|pytest|go test|make|cargo|mvn|gradle|rspec|jest|vitest|bd)\b\s+\S/i;
|
|
70
|
+
const VERIFICATION_TEST_RE = /\b[\w-]+\.(test|spec)\.\w+\b|\btest\(['"]/i;
|
|
71
|
+
const VERIFICATION_ATTESTATION_RE = /\b(cx-[\w-]+|reviewer|qa|security|tester)\s*:\s*(APPROVED|PASS(?:ED)?|FAIL(?:ED)?|BLOCKED|REJECTED|\d+\s*(?:tests?|assertions?|%|percent))/i;
|
|
72
|
+
|
|
73
|
+
function isReverifiableEvidence(entry) {
|
|
74
|
+
const text = typeof entry === "string" ? entry : entry == null ? "" : JSON.stringify(entry);
|
|
75
|
+
if (!text.trim()) return false;
|
|
76
|
+
return (
|
|
77
|
+
VERIFICATION_URL_RE.test(text) ||
|
|
78
|
+
VERIFICATION_FILE_RE.test(text) ||
|
|
79
|
+
VERIFICATION_COMMAND_RE.test(text) ||
|
|
80
|
+
VERIFICATION_TEST_RE.test(text) ||
|
|
81
|
+
VERIFICATION_ATTESTATION_RE.test(text)
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function hasReverifiableEvidence(entries) {
|
|
86
|
+
return Array.isArray(entries) && entries.some(isReverifiableEvidence);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// The done-gate applies to every phase where "done" claims real work occurred
|
|
90
|
+
// (research/plan tasks close on written artifacts the workflow already tracks
|
|
91
|
+
// elsewhere, not on independently re-verifiable evidence).
|
|
92
|
+
|
|
93
|
+
const VERIFICATION_GATED_PHASES = new Set(["implement", "validate", "operate"]);
|
|
94
|
+
|
|
58
95
|
function slugify(value) {
|
|
59
96
|
return String(value || "workflow")
|
|
60
97
|
.toLowerCase()
|
|
@@ -220,7 +257,7 @@ export function buildTaskPacketFromIntent(request, options = {}) {
|
|
|
220
257
|
const defaults = {
|
|
221
258
|
title: options.title || request || "User-requested task",
|
|
222
259
|
phase: options.phase || (route.intent === "research" ? "research" : route.intent === "fix" ? "implement" : route.intent === "evaluation" ? "validate" : route.intent === "implementation" ? "implement" : "implement"),
|
|
223
|
-
owner: options.owner || (route.workCategory === "analysis" ? "cx-architect" : route.workCategory === "writing" ? "cx-
|
|
260
|
+
owner: options.owner || (route.workCategory === "analysis" ? "cx-architect" : route.workCategory === "writing" ? "cx-operations" : route.workCategory === "visual" ? "cx-designer" : route.workCategory === "deep" ? "cx-engineer" : "cx-engineer"),
|
|
224
261
|
status: "todo",
|
|
225
262
|
dependsOn: options.dependsOn || [],
|
|
226
263
|
files: options.files || [],
|
|
@@ -237,7 +274,6 @@ export function buildTaskPacketFromIntent(request, options = {}) {
|
|
|
237
274
|
workCategory: route.workCategory,
|
|
238
275
|
track: route.track,
|
|
239
276
|
specialists: route.specialists || [],
|
|
240
|
-
dispatchPlan: route.dispatchPlan || null,
|
|
241
277
|
request: String(request || ""),
|
|
242
278
|
},
|
|
243
279
|
};
|
|
@@ -413,7 +449,7 @@ export function addTasksFromPlan(root, markdown, options = {}) {
|
|
|
413
449
|
|
|
414
450
|
export function updateTask(root, key, patch) {
|
|
415
451
|
const workflow = loadWorkflow(root);
|
|
416
|
-
if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow
|
|
452
|
+
if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow new` first.");
|
|
417
453
|
const cleanPatch = Object.fromEntries(Object.entries(patch).filter(([, value]) => value !== undefined));
|
|
418
454
|
let found = false;
|
|
419
455
|
workflow.tasks = (workflow.tasks || []).map((task) => {
|
|
@@ -433,14 +469,20 @@ export function updateTask(root, key, patch) {
|
|
|
433
469
|
if (cleanPatch.status && !VALID_STATUS.has(cleanPatch.status)) throw new Error(`Invalid status: ${cleanPatch.status}`);
|
|
434
470
|
if (cleanPatch.status === "done") {
|
|
435
471
|
const task = workflow.tasks.find((t) => t.key === key);
|
|
436
|
-
if (task && task.phase
|
|
472
|
+
if (task && VERIFICATION_GATED_PHASES.has(task.phase) && workflow.alignment?.verificationRequiredBeforeDone !== false) {
|
|
437
473
|
const mergedVerification = cleanPatch.verification ?? task.verification ?? [];
|
|
438
474
|
if (mergedVerification.length === 0) {
|
|
439
475
|
throw new Error(
|
|
440
|
-
`Cannot mark
|
|
476
|
+
`Cannot mark ${task.phase}-phase task "${key}" as done without verification evidence. ` +
|
|
441
477
|
`Add cx-reviewer and cx-qa results to task.verification before marking done.`
|
|
442
478
|
);
|
|
443
479
|
}
|
|
480
|
+
if (!hasReverifiableEvidence(mergedVerification)) {
|
|
481
|
+
throw new Error(
|
|
482
|
+
`Cannot mark ${task.phase}-phase task "${key}" as done: verification evidence is not re-verifiable. ` +
|
|
483
|
+
`Add a runnable command, file path, URL, test reference, or a cx-reviewer/cx-qa attestation.`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
444
486
|
}
|
|
445
487
|
}
|
|
446
488
|
if (cleanPatch.status === "in-progress" || cleanPatch.status === "blocked_needs_user") workflow.currentTaskKey = key;
|
|
@@ -454,7 +496,7 @@ export function transitionPhase(root, phase, status = "in-progress") {
|
|
|
454
496
|
phase = normalizePhase(phase);
|
|
455
497
|
if (!phase) throw new Error(`Invalid phase: ${phase}`);
|
|
456
498
|
const workflow = loadWorkflow(root);
|
|
457
|
-
if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow
|
|
499
|
+
if (!workflow) throw new Error("No .cx/workflow.json found. Run `construct workflow new` first.");
|
|
458
500
|
workflow.phase = phase;
|
|
459
501
|
workflow.phases = workflow.phases || {};
|
|
460
502
|
for (const p of PHASES) {
|
|
@@ -469,7 +511,7 @@ export function alignmentFindings(workflow) {
|
|
|
469
511
|
return [{
|
|
470
512
|
severity: "HIGH",
|
|
471
513
|
issue: "No .cx/workflow.json found",
|
|
472
|
-
fix: "Run `construct workflow
|
|
514
|
+
fix: "Run `construct workflow new \"<title>\"` at the project root."
|
|
473
515
|
}];
|
|
474
516
|
}
|
|
475
517
|
const findings = [];
|
|
@@ -503,6 +545,8 @@ export function alignmentFindings(workflow) {
|
|
|
503
545
|
}
|
|
504
546
|
if (task.status === "done" && (!Array.isArray(task.verification) || task.verification.length === 0)) {
|
|
505
547
|
findings.push({ severity: "HIGH", task: task.key, issue: "Done task has no verification evidence", fix: "Record commands, checks, or review evidence before marking done." });
|
|
548
|
+
} else if (task.status === "done" && Array.isArray(task.verification) && task.verification.length > 0 && VERIFICATION_GATED_PHASES.has(task.phase) && !hasReverifiableEvidence(task.verification)) {
|
|
549
|
+
findings.push({ severity: "HIGH", task: task.key, issue: "Done task's verification evidence is not re-verifiable", fix: "Record a runnable command, file path, URL, test reference, or a cx-reviewer/cx-qa attestation." });
|
|
506
550
|
}
|
|
507
551
|
for (const dep of task.dependsOn || []) {
|
|
508
552
|
const depTask = tasks.find((candidate) => candidate.key === dep);
|
|
@@ -780,7 +824,7 @@ export function runWorkflowCli(argv = process.argv.slice(2), root = process.cwd(
|
|
|
780
824
|
throw new Error(`Unknown workflow subcommand: ${command}`);
|
|
781
825
|
}
|
|
782
826
|
|
|
783
|
-
if (import.meta.url
|
|
827
|
+
if (isMainModule(import.meta.url)) {
|
|
784
828
|
try {
|
|
785
829
|
runWorkflowCli(process.argv.slice(2), process.cwd());
|
|
786
830
|
} catch (error) {
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflows/liveness.mjs — workflow manifest liveness validation (LMCP-C11).
|
|
3
|
+
*
|
|
4
|
+
* Schema validation (validate.mjs) covers shape only: required fields, types,
|
|
5
|
+
* compatVersion. A roleChain entry naming a specialist that does not exist, a
|
|
6
|
+
* workflow whose handoff chain loops back on itself, and a workflow no surface
|
|
7
|
+
* can ever invoke all pass schema validation and fail only when a user
|
|
8
|
+
* actually runs the workflow. checkWorkflowLiveness runs four checks per
|
|
9
|
+
* manifest, over the already-loaded manifest list and the pack-aware merged
|
|
10
|
+
* specialist registry (LMCP-E1 precedence: project > pack > builtin, via
|
|
11
|
+
* loadAllPacks):
|
|
12
|
+
*
|
|
13
|
+
* 1. roleChain resolution — every role resolves to an installed specialist.
|
|
14
|
+
* 2. skill existence — every skill the resolved specialists declare
|
|
15
|
+
* exists on disk (the concrete capability a
|
|
16
|
+
* resolved roleChain entry brings with it).
|
|
17
|
+
* 3. acyclic handoff graph — the roleChain's sequential handoffs
|
|
18
|
+
* (chain[i] -> chain[i+1]) never loop back to
|
|
19
|
+
* an earlier role in the same workflow.
|
|
20
|
+
* 4. surface/mode reachability — every workflow declares at least one
|
|
21
|
+
* surface and one mode, so some entrypoint can
|
|
22
|
+
* reach it.
|
|
23
|
+
*
|
|
24
|
+
* Returns { violations } without throwing and without classifying severity.
|
|
25
|
+
* Every violation — a bad roleChain entry, a missing skill file, a handoff
|
|
26
|
+
* cycle, or an unreachable workflow — is reported as a plain message; the
|
|
27
|
+
* caller (lib/graph/validate.mjs) applies mode policy uniformly, matching
|
|
28
|
+
* the existing C2 convention: liveness reports facts, the graph validator
|
|
29
|
+
* classifies error vs warning per deployment mode.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
33
|
+
import { dirname, join, resolve } from 'node:path';
|
|
34
|
+
import { fileURLToPath } from 'node:url';
|
|
35
|
+
|
|
36
|
+
import { loadAllPacks } from '../packs/loader.mjs';
|
|
37
|
+
|
|
38
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
|
|
40
|
+
/** Root of the Construct package (parent of lib/); skills/ lives here, not under an arbitrary project rootDir. */
|
|
41
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the set of installed specialist ids (pack-aware, E1 precedence) and a
|
|
45
|
+
* lookup from bare role id ("architect") to specialist skills, by reading the
|
|
46
|
+
* merged pack list's specialists/org directories directly. loadAllPacks()
|
|
47
|
+
* already applies builtin < user < project precedence; the core pack's
|
|
48
|
+
* specialists come from specialists/org/specialists on rootDir.
|
|
49
|
+
*
|
|
50
|
+
* @param {{ rootDir?: string, packRoots?: string[] }} opts
|
|
51
|
+
* @returns {{ specialistIds: Set<string>, skillsByRole: Map<string, string[]> }}
|
|
52
|
+
*/
|
|
53
|
+
function resolveSpecialistRegistry({ rootDir = process.cwd() } = {}) {
|
|
54
|
+
const { packs } = loadAllPacks({ rootDir });
|
|
55
|
+
|
|
56
|
+
const specialistIds = new Set();
|
|
57
|
+
const skillsByRole = new Map();
|
|
58
|
+
|
|
59
|
+
for (const pack of packs) {
|
|
60
|
+
const ids = Array.isArray(pack.specialists) ? pack.specialists : [];
|
|
61
|
+
for (const id of ids) specialistIds.add(id);
|
|
62
|
+
|
|
63
|
+
const sourceDir = pack._sourceDir || pack._packDir;
|
|
64
|
+
if (!sourceDir) continue;
|
|
65
|
+
|
|
66
|
+
for (const id of ids) {
|
|
67
|
+
const specPath = join(sourceDir, 'specialists', `${id}.json`);
|
|
68
|
+
if (!existsSync(specPath)) continue;
|
|
69
|
+
try {
|
|
70
|
+
const raw = JSON.parse(readFileSync(specPath, 'utf8'));
|
|
71
|
+
const roleId = id.startsWith('cx-') ? id.slice(3) : id;
|
|
72
|
+
skillsByRole.set(roleId, Array.isArray(raw.skills) ? raw.skills : []);
|
|
73
|
+
} catch {
|
|
74
|
+
// Malformed specialist file — resolution failure surfaces separately
|
|
75
|
+
// via roleChain checks when the role is referenced; skip here.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return { specialistIds, skillsByRole };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Normalize a roleChain entry to the registry-prefixed specialist id.
|
|
85
|
+
* roleChain entries are bare role ids ("architect"); the registry stores
|
|
86
|
+
* "cx-architect". Entries already carrying the prefix pass through.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} role
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function specialistIdFor(role) {
|
|
92
|
+
return role.startsWith('cx-') ? role : `cx-${role}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Detect a cycle in the sequential handoff graph of a roleChain: nodes are
|
|
97
|
+
* distinct roles, edges are chain[i] -> chain[i+1]. A role that reappears
|
|
98
|
+
* later in the same chain after handing off to a different role creates a
|
|
99
|
+
* back-edge, forming a cycle (e.g. [a, b, a] => a->b, b->a).
|
|
100
|
+
*
|
|
101
|
+
* @param {string[]} roleChain
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
*/
|
|
104
|
+
function hasHandoffCycle(roleChain) {
|
|
105
|
+
const edges = new Map();
|
|
106
|
+
for (let i = 0; i < roleChain.length - 1; i++) {
|
|
107
|
+
const from = roleChain[i];
|
|
108
|
+
const to = roleChain[i + 1];
|
|
109
|
+
if (from === to) continue;
|
|
110
|
+
if (!edges.has(from)) edges.set(from, new Set());
|
|
111
|
+
edges.get(from).add(to);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
115
|
+
const color = new Map();
|
|
116
|
+
for (const node of roleChain) if (!color.has(node)) color.set(node, WHITE);
|
|
117
|
+
|
|
118
|
+
function visit(node) {
|
|
119
|
+
color.set(node, GRAY);
|
|
120
|
+
for (const next of edges.get(node) || []) {
|
|
121
|
+
const c = color.get(next);
|
|
122
|
+
if (c === GRAY) return true;
|
|
123
|
+
if (c === WHITE && visit(next)) return true;
|
|
124
|
+
}
|
|
125
|
+
color.set(node, BLACK);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const node of color.keys()) {
|
|
130
|
+
if (color.get(node) === WHITE && visit(node)) return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* checkWorkflowLiveness(manifests, opts)
|
|
137
|
+
*
|
|
138
|
+
* Runs the four liveness checks over an already-loaded, already
|
|
139
|
+
* schema-validated manifest list. Manifests missing roleChain/surfaces/modes
|
|
140
|
+
* entirely are skipped for the checks that need them — schema validation
|
|
141
|
+
* already flags genuinely malformed manifests; liveness only adds checks
|
|
142
|
+
* schema validation cannot express.
|
|
143
|
+
*
|
|
144
|
+
* @param {object[]} manifests - Loaded manifests, each carrying `_filePath`.
|
|
145
|
+
* @param {{ rootDir?: string, packRoots?: string[], packageRoot?: string }} [opts]
|
|
146
|
+
* rootDir selects the project whose pack tiers are merged (specialist
|
|
147
|
+
* resolution); packageRoot (defaults to the Construct install) is where
|
|
148
|
+
* skills/ is read from for the skill-existence check.
|
|
149
|
+
* @returns {{ violations: string[] }}
|
|
150
|
+
*/
|
|
151
|
+
export function checkWorkflowLiveness(manifests, opts = {}) {
|
|
152
|
+
const violations = [];
|
|
153
|
+
|
|
154
|
+
const { specialistIds, skillsByRole } = resolveSpecialistRegistry(opts);
|
|
155
|
+
|
|
156
|
+
for (const manifest of manifests) {
|
|
157
|
+
// Embed manifests are a workflow specialization scheduled by the embed
|
|
158
|
+
// daemon, not reached through a surface+mode entrypoint or a role chain —
|
|
159
|
+
// their liveness is enforced by the embed-capability loader, so the
|
|
160
|
+
// executable-workflow liveness checks below do not apply.
|
|
161
|
+
if (manifest.type === 'embed') continue;
|
|
162
|
+
|
|
163
|
+
const label = manifest._filePath || manifest.id || '(unknown manifest)';
|
|
164
|
+
const roleChain = Array.isArray(manifest.roleChain) ? manifest.roleChain : [];
|
|
165
|
+
|
|
166
|
+
// Check 1: roleChain resolution against the pack-aware merged registry.
|
|
167
|
+
for (const role of roleChain) {
|
|
168
|
+
const specId = specialistIdFor(role);
|
|
169
|
+
if (!specialistIds.has(specId)) {
|
|
170
|
+
violations.push(`${label}: roleChain entry '${role}' does not resolve to an installed specialist (expected '${specId}')`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Check 2: skill existence for every resolved role's declared skills.
|
|
175
|
+
// skills/ is a package-level resource (shared across projects), so it
|
|
176
|
+
// resolves against packageRoot, not the project rootDir.
|
|
177
|
+
const packageRoot = opts.packageRoot || PACKAGE_ROOT;
|
|
178
|
+
for (const role of roleChain) {
|
|
179
|
+
const skills = skillsByRole.get(role);
|
|
180
|
+
if (!skills) continue;
|
|
181
|
+
for (const skillId of skills) {
|
|
182
|
+
const skillPath = join(packageRoot, 'skills', `${skillId}.md`);
|
|
183
|
+
if (!existsSync(skillPath)) {
|
|
184
|
+
violations.push(`${label}: role '${role}' declares skill '${skillId}' with no matching file at skills/${skillId}.md`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Check 3: acyclic handoff graph.
|
|
190
|
+
if (roleChain.length > 1 && hasHandoffCycle(roleChain)) {
|
|
191
|
+
violations.push(`${label}: roleChain contains a circular handoff (${roleChain.join(' -> ')})`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Check 4: reachability from at least one declared surface and mode.
|
|
195
|
+
const surfaces = Array.isArray(manifest.surfaces) ? manifest.surfaces : [];
|
|
196
|
+
const modes = Array.isArray(manifest.modes) ? manifest.modes : [];
|
|
197
|
+
if (surfaces.length === 0 || modes.length === 0) {
|
|
198
|
+
violations.push(`${label}: workflow '${manifest.id}' is unreachable — declares ${surfaces.length === 0 ? 'no surfaces' : `surfaces [${surfaces.join(', ')}]`} and ${modes.length === 0 ? 'no modes' : `modes [${modes.join(', ')}]`}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { violations };
|
|
203
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/workflows/loader.mjs — workflow manifest directory loader and merger.
|
|
3
|
+
*
|
|
4
|
+
* Three manifest tiers exist: builtin (shipped with Construct in
|
|
5
|
+
* lib/embedded-contract/workflows/), pack (in {pack_root}/workflows/), and
|
|
6
|
+
* project (in .cx/workflows/). Project manifests take highest precedence;
|
|
7
|
+
* pack manifests override builtin.
|
|
8
|
+
*
|
|
9
|
+
* `loadWorkflowManifestsFromDir` reads every *.manifest.json in a directory and
|
|
10
|
+
* validates each one. Errors are collected without throwing. `mergeWorkflowManifests`
|
|
11
|
+
* merges up to three tiers by id. `resolveWorkflowManifestDirs` returns the
|
|
12
|
+
* canonical paths for each tier.
|
|
13
|
+
*
|
|
14
|
+
* Provenance (LMCP-D3): every loaded manifest carries `_source` ('builtin' |
|
|
15
|
+
* 'pack' | 'project') and `_filePath`. When a higher tier overrides a lower
|
|
16
|
+
* tier's id, `mergeWorkflowManifests` records the overridden entries on the
|
|
17
|
+
* winner as `_shadowedBy` so a caller (e.g. `construct graph explain`) can
|
|
18
|
+
* show which source won and what it overrode without re-reading disk.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
22
|
+
import { dirname, join, resolve } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
import { validateWorkflowManifest } from './validate.mjs';
|
|
26
|
+
|
|
27
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
28
|
+
|
|
29
|
+
/** Root of the Construct package (parent of lib/). */
|
|
30
|
+
const PACKAGE_ROOT = resolve(__dirname, '..', '..');
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* loadWorkflowManifestsFromDir(dirPath, { strict } = {})
|
|
34
|
+
*
|
|
35
|
+
* Reads all `*.manifest.json` files in dirPath, validates each one, and
|
|
36
|
+
* returns a result object. Files that fail JSON parsing or validation are
|
|
37
|
+
* collected in `errors`; valid manifests are collected in `manifests`.
|
|
38
|
+
*
|
|
39
|
+
* When `strict` is true, manifests are validated with the strict-mode
|
|
40
|
+
* hardening checks (unknown field rejection).
|
|
41
|
+
*
|
|
42
|
+
* @param {string} dirPath - Absolute path to search.
|
|
43
|
+
* @param {{ strict?: boolean, source?: string }} [opts]
|
|
44
|
+
* @returns {{ manifests: object[], errors: string[] }}
|
|
45
|
+
*/
|
|
46
|
+
export function loadWorkflowManifestsFromDir(dirPath, { strict = false, source = 'unknown' } = {}) {
|
|
47
|
+
const manifests = [];
|
|
48
|
+
const errors = [];
|
|
49
|
+
|
|
50
|
+
if (!dirPath || !existsSync(dirPath)) {
|
|
51
|
+
return { manifests, errors };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
let entries;
|
|
55
|
+
try {
|
|
56
|
+
entries = readdirSync(dirPath, { withFileTypes: true });
|
|
57
|
+
} catch (err) {
|
|
58
|
+
errors.push(`${dirPath}: failed to read directory (${err.message})`);
|
|
59
|
+
return { manifests, errors };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const manifestFiles = entries
|
|
63
|
+
.filter((e) => e.isFile() && e.name.endsWith('.manifest.json'))
|
|
64
|
+
.map((e) => join(dirPath, e.name))
|
|
65
|
+
.sort();
|
|
66
|
+
|
|
67
|
+
for (const filePath of manifestFiles) {
|
|
68
|
+
let parsed;
|
|
69
|
+
try {
|
|
70
|
+
parsed = JSON.parse(readFileSync(filePath, 'utf8'));
|
|
71
|
+
} catch (err) {
|
|
72
|
+
errors.push(`${filePath}: failed to parse JSON (${err.message})`);
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const result = validateWorkflowManifest(parsed, { filePath, strict });
|
|
77
|
+
if (!result.valid) {
|
|
78
|
+
errors.push(...result.errors);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
manifests.push({ ...parsed, _filePath: filePath, _source: source });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { manifests, errors };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* mergeWorkflowManifests(builtin, pack, project)
|
|
90
|
+
*
|
|
91
|
+
* Merges three manifest arrays by `id`. Precedence (highest wins): project >
|
|
92
|
+
* pack > builtin. When multiple tiers declare the same id, only the
|
|
93
|
+
* highest-precedence entry is kept.
|
|
94
|
+
*
|
|
95
|
+
* @param {object[]} builtin
|
|
96
|
+
* @param {object[]} pack
|
|
97
|
+
* @param {object[]} project
|
|
98
|
+
* @returns {object[]}
|
|
99
|
+
*/
|
|
100
|
+
export function mergeWorkflowManifests(builtin = [], pack = [], project = []) {
|
|
101
|
+
const map = new Map();
|
|
102
|
+
const shadowed = new Map();
|
|
103
|
+
|
|
104
|
+
const layer = (list) => {
|
|
105
|
+
for (const m of list) {
|
|
106
|
+
const existing = map.get(m.id);
|
|
107
|
+
if (existing) {
|
|
108
|
+
const log = shadowed.get(m.id) || [];
|
|
109
|
+
log.push({ source: existing._source || 'unknown', filePath: existing._filePath || null });
|
|
110
|
+
shadowed.set(m.id, log);
|
|
111
|
+
}
|
|
112
|
+
map.set(m.id, m);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Load lowest priority first so higher priority layers overwrite.
|
|
117
|
+
layer(builtin);
|
|
118
|
+
layer(pack);
|
|
119
|
+
layer(project);
|
|
120
|
+
|
|
121
|
+
for (const [id, winner] of map) {
|
|
122
|
+
const log = shadowed.get(id);
|
|
123
|
+
if (log?.length) winner._shadowedBy = log;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [...map.values()];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* resolveWorkflowManifestDirs({ rootDir, packRoots })
|
|
131
|
+
*
|
|
132
|
+
* Returns the canonical directory paths for each workflow manifest tier.
|
|
133
|
+
*
|
|
134
|
+
* @param {{ rootDir?: string, packRoots?: string[] }} opts
|
|
135
|
+
* @returns {{ builtin: string, pack: string[], project: string }}
|
|
136
|
+
*/
|
|
137
|
+
export function resolveWorkflowManifestDirs({ rootDir = process.cwd(), packRoots = [] } = {}) {
|
|
138
|
+
return {
|
|
139
|
+
builtin: join(PACKAGE_ROOT, 'lib', 'embedded-contract', 'workflows'),
|
|
140
|
+
pack: (packRoots || []).map((pr) => join(pr, 'workflows')),
|
|
141
|
+
project: join(rootDir, '.cx', 'workflows'),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* loadAllWorkflows(opts)
|
|
147
|
+
*
|
|
148
|
+
* Convenience function that discovers, loads, and merges all workflow manifests
|
|
149
|
+
* across all three tiers. Returns the merged list of manifests and any errors
|
|
150
|
+
* encountered.
|
|
151
|
+
*
|
|
152
|
+
* @param {{ rootDir?: string, packRoots?: string[], strict?: boolean }} opts
|
|
153
|
+
* @returns {{ workflows: object[], errors: string[] }}
|
|
154
|
+
*/
|
|
155
|
+
export function loadAllWorkflows(opts = {}) {
|
|
156
|
+
const { rootDir, packRoots = [], strict = false } = opts;
|
|
157
|
+
const errors = [];
|
|
158
|
+
|
|
159
|
+
const dirs = resolveWorkflowManifestDirs({ rootDir, packRoots });
|
|
160
|
+
|
|
161
|
+
const builtin = loadWorkflowManifestsFromDir(dirs.builtin, { strict, source: 'builtin' });
|
|
162
|
+
errors.push(...builtin.errors);
|
|
163
|
+
|
|
164
|
+
const packManifests = [];
|
|
165
|
+
for (const packDir of dirs.pack) {
|
|
166
|
+
const result = loadWorkflowManifestsFromDir(packDir, { strict, source: 'pack' });
|
|
167
|
+
packManifests.push(...result.manifests);
|
|
168
|
+
errors.push(...result.errors);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const project = loadWorkflowManifestsFromDir(dirs.project, { strict, source: 'project' });
|
|
172
|
+
errors.push(...project.errors);
|
|
173
|
+
|
|
174
|
+
const workflows = mergeWorkflowManifests(builtin.manifests, packManifests, project.manifests);
|
|
175
|
+
|
|
176
|
+
return { workflows, errors };
|
|
177
|
+
}
|