@geraldmaron/construct 1.4.2 → 1.5.1
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 +57 -45
- 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 +273 -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 +104 -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,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/define.mjs — flow definition loader and load-time validator.
|
|
3
|
+
*
|
|
4
|
+
* defineFlow() is the single gate a flow definition passes through before the
|
|
5
|
+
* engine ever sees it: a bad state schema, a dangling step reference, or a
|
|
6
|
+
* mutating step declaring fanOut all fail here, at load time, rather than as
|
|
7
|
+
* a runtime convention the engine merely documents. loadFlow() adds a file
|
|
8
|
+
* source on top: a JS module (.mjs/.js) is dynamically imported and must
|
|
9
|
+
* export the flow definition, run/router functions included, as its default
|
|
10
|
+
* export. A JSON file holds only data — no function can live in JSON — so its
|
|
11
|
+
* steps carry structure (workerBackend, inputs, budget, fanOut, synthesis,
|
|
12
|
+
* waitFor) and loadFlow's `handlers` option supplies the per-step run/router
|
|
13
|
+
* functions, merged onto the parsed steps before validation. Both paths
|
|
14
|
+
* converge on defineFlow(), so there is exactly one place fan-out/join/schema
|
|
15
|
+
* rules live.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import path from 'node:path';
|
|
19
|
+
import { pathToFileURL } from 'node:url';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
|
|
22
|
+
import { WORKER_BACKENDS, JOIN_MODES, TERMINAL } from './constants.mjs';
|
|
23
|
+
import { FlowDefinitionError } from './errors.mjs';
|
|
24
|
+
|
|
25
|
+
function isPlainObject(value) {
|
|
26
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateWaitFor(waitFor, stepName, stepNames, errors) {
|
|
30
|
+
if (waitFor === undefined) return;
|
|
31
|
+
if (!isPlainObject(waitFor) || !JOIN_MODES.includes(waitFor.mode) || !Array.isArray(waitFor.steps) || waitFor.steps.length === 0) {
|
|
32
|
+
errors.push(`step "${stepName}": waitFor must be { mode: 'all' | 'any', steps: string[] } with at least one step`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
for (const predecessor of waitFor.steps) {
|
|
36
|
+
if (!stepNames.has(predecessor)) {
|
|
37
|
+
errors.push(`step "${stepName}": waitFor references unknown step "${predecessor}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateStep(name, step, stepNames, errors) {
|
|
43
|
+
if (!isPlainObject(step)) {
|
|
44
|
+
errors.push(`step "${name}": must be an object`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (!WORKER_BACKENDS.includes(step.workerBackend)) {
|
|
48
|
+
errors.push(`step "${name}": workerBackend must be one of ${WORKER_BACKENDS.join(', ')}`);
|
|
49
|
+
}
|
|
50
|
+
if (typeof step.run !== 'function') {
|
|
51
|
+
errors.push(`step "${name}": run must be a function`);
|
|
52
|
+
}
|
|
53
|
+
if (step.router !== undefined && typeof step.router !== 'function') {
|
|
54
|
+
errors.push(`step "${name}": router must be a function when provided`);
|
|
55
|
+
}
|
|
56
|
+
if (step.inputs !== undefined && !Array.isArray(step.inputs)) {
|
|
57
|
+
errors.push(`step "${name}": inputs must be an array of state keys when provided`);
|
|
58
|
+
}
|
|
59
|
+
if (step.budget !== undefined && !(typeof step.budget === 'number' && step.budget > 0)) {
|
|
60
|
+
errors.push(`step "${name}": budget must be a positive number when provided`);
|
|
61
|
+
}
|
|
62
|
+
if (step.fanOut) {
|
|
63
|
+
if (step.readOnly !== true) {
|
|
64
|
+
errors.push(`step "${name}": fanOut requires readOnly: true (fan-out is restricted to read-only backends)`);
|
|
65
|
+
}
|
|
66
|
+
if (typeof step.synthesis !== 'string' || !step.synthesis) {
|
|
67
|
+
errors.push(`step "${name}": fanOut requires a synthesis step name`);
|
|
68
|
+
} else if (!stepNames.has(step.synthesis)) {
|
|
69
|
+
errors.push(`step "${name}": synthesis references unknown step "${step.synthesis}"`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
validateWaitFor(step.waitFor, name, stepNames, errors);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function defineFlow(definition) {
|
|
76
|
+
const errors = [];
|
|
77
|
+
if (!isPlainObject(definition)) {
|
|
78
|
+
throw new FlowDefinitionError('flow definition must be an object', ['flow definition must be an object']);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const { id = null, stateSchema, startStep, steps } = definition;
|
|
82
|
+
|
|
83
|
+
if (!isPlainObject(stateSchema)) errors.push('stateSchema must be an object');
|
|
84
|
+
if (typeof startStep !== 'string' || !startStep) errors.push('startStep must be a non-empty string');
|
|
85
|
+
if (!isPlainObject(steps) || Object.keys(steps).length === 0) errors.push('steps must be a non-empty object');
|
|
86
|
+
|
|
87
|
+
if (errors.length > 0) {
|
|
88
|
+
throw new FlowDefinitionError(`flow "${id ?? '(unnamed)'}" is invalid`, errors);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const stepNames = new Set(Object.keys(steps));
|
|
92
|
+
if (!stepNames.has(startStep)) errors.push(`startStep "${startStep}" is not a declared step`);
|
|
93
|
+
|
|
94
|
+
for (const [name, step] of Object.entries(steps)) {
|
|
95
|
+
validateStep(name, step, stepNames, errors);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (errors.length > 0) {
|
|
99
|
+
throw new FlowDefinitionError(`flow "${id ?? '(unnamed)'}" is invalid`, errors);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const normalizedSteps = {};
|
|
103
|
+
for (const [name, step] of Object.entries(steps)) {
|
|
104
|
+
normalizedSteps[name] = {
|
|
105
|
+
workerBackend: step.workerBackend,
|
|
106
|
+
inputs: step.inputs ? [...step.inputs] : [],
|
|
107
|
+
run: step.run,
|
|
108
|
+
router: step.router ?? (() => TERMINAL),
|
|
109
|
+
budget: step.budget ?? null,
|
|
110
|
+
readOnly: Boolean(step.readOnly),
|
|
111
|
+
fanOut: Boolean(step.fanOut),
|
|
112
|
+
synthesis: step.synthesis ?? null,
|
|
113
|
+
waitFor: step.waitFor ? { mode: step.waitFor.mode, steps: [...step.waitFor.steps] } : null,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return Object.freeze({
|
|
118
|
+
id,
|
|
119
|
+
stateSchema,
|
|
120
|
+
startStep,
|
|
121
|
+
steps: Object.freeze(normalizedSteps),
|
|
122
|
+
stepOrder: Object.freeze(Object.keys(normalizedSteps)),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export async function loadFlow(source, { handlers = {} } = {}) {
|
|
127
|
+
const resolved = path.resolve(source);
|
|
128
|
+
const ext = path.extname(resolved);
|
|
129
|
+
|
|
130
|
+
if (ext === '.json') {
|
|
131
|
+
const raw = fs.readFileSync(resolved, 'utf8');
|
|
132
|
+
const definition = JSON.parse(raw);
|
|
133
|
+
const steps = {};
|
|
134
|
+
for (const [name, step] of Object.entries(definition.steps || {})) {
|
|
135
|
+
steps[name] = { ...step, ...(handlers[name] || {}) };
|
|
136
|
+
}
|
|
137
|
+
return defineFlow({ ...definition, steps });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const mod = await import(pathToFileURL(resolved).href);
|
|
141
|
+
const definition = mod.default ?? mod.flowDefinition;
|
|
142
|
+
if (!definition) {
|
|
143
|
+
throw new FlowDefinitionError(`flow module "${source}" has no default export`, [`flow module "${source}" has no default export`]);
|
|
144
|
+
}
|
|
145
|
+
return defineFlow(definition);
|
|
146
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/engine.mjs — deterministic step executor and run driver.
|
|
3
|
+
*
|
|
4
|
+
* runStep() executes exactly one named step: it enforces the step's effort
|
|
5
|
+
* budget before calling the step's own logic, merges the returned state delta
|
|
6
|
+
* back through state.mjs's schema-validated transition, and always returns a
|
|
7
|
+
* structured StepResult (status/timing/usage/error) — never a bare value and
|
|
8
|
+
* never a silent truncation on budget exhaustion.
|
|
9
|
+
*
|
|
10
|
+
* createRun()/advanceRun() drive a whole flow one ready step at a time. A run
|
|
11
|
+
* is plain, cloned-not-mutated data (state, frontier, completed set, join
|
|
12
|
+
* progress, usage, history), inspectable and persistable between ticks —
|
|
13
|
+
* checkpoint/resume is a later increment, addable on top of that shape
|
|
14
|
+
* without an engine rewrite.
|
|
15
|
+
*
|
|
16
|
+
* Deterministic ordering: when more than one step is ready in the same tick,
|
|
17
|
+
* the engine always runs the one that appears first in the flow definition's
|
|
18
|
+
* `steps` object (declaration order), regardless of the order routers added
|
|
19
|
+
* them to the frontier or which fan-out branch finished first. And-joins
|
|
20
|
+
* (waitFor: { mode: 'all' }) hold a step out of the frontier until every
|
|
21
|
+
* listed predecessor has completed and routed into it; the default (or an
|
|
22
|
+
* explicit `mode: 'any'`) admits a step to the frontier the first time any
|
|
23
|
+
* predecessor routes into it, which is how branch reconvergence works.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { TERMINAL, STEP_STATUS, RUN_STATUS } from './constants.mjs';
|
|
27
|
+
import { transition, createInitialState } from './state.mjs';
|
|
28
|
+
|
|
29
|
+
function pickInputs(state, keys) {
|
|
30
|
+
const input = {};
|
|
31
|
+
for (const key of keys) input[key] = state[key];
|
|
32
|
+
return input;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function runStep(flow, stepName, state, { consumedSoFar = 0 } = {}) {
|
|
36
|
+
const step = flow.steps[stepName];
|
|
37
|
+
const startedAt = Date.now();
|
|
38
|
+
const input = pickInputs(state, step.inputs);
|
|
39
|
+
|
|
40
|
+
if (step.budget !== null && consumedSoFar >= step.budget) {
|
|
41
|
+
const finishedAt = Date.now();
|
|
42
|
+
return {
|
|
43
|
+
step: stepName,
|
|
44
|
+
status: STEP_STATUS.BUDGET_EXHAUSTED,
|
|
45
|
+
workerBackend: step.workerBackend,
|
|
46
|
+
input,
|
|
47
|
+
stateDelta: null,
|
|
48
|
+
usage: { consumed: 0, total: consumedSoFar, budget: step.budget },
|
|
49
|
+
startedAt,
|
|
50
|
+
finishedAt,
|
|
51
|
+
durationMs: finishedAt - startedAt,
|
|
52
|
+
error: { code: 'BUDGET_EXHAUSTED', message: `step "${stepName}" exhausted its effort budget (${step.budget})` },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let result;
|
|
57
|
+
try {
|
|
58
|
+
result = await step.run(input, { state, stepName, workerBackend: step.workerBackend });
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const finishedAt = Date.now();
|
|
61
|
+
return {
|
|
62
|
+
step: stepName,
|
|
63
|
+
status: STEP_STATUS.ERROR,
|
|
64
|
+
workerBackend: step.workerBackend,
|
|
65
|
+
input,
|
|
66
|
+
stateDelta: null,
|
|
67
|
+
usage: null,
|
|
68
|
+
startedAt,
|
|
69
|
+
finishedAt,
|
|
70
|
+
durationMs: finishedAt - startedAt,
|
|
71
|
+
error: { code: 'STEP_THREW', message: err.message },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const delta = result?.state ?? {};
|
|
76
|
+
const consumed = result?.usage?.consumed ?? 0;
|
|
77
|
+
const total = consumedSoFar + consumed;
|
|
78
|
+
const usage = step.budget !== null || consumed > 0 ? { consumed, total, budget: step.budget } : null;
|
|
79
|
+
|
|
80
|
+
const transitioned = transition(flow.stateSchema, state, delta);
|
|
81
|
+
const finishedAt = Date.now();
|
|
82
|
+
const durationMs = finishedAt - startedAt;
|
|
83
|
+
|
|
84
|
+
if (!transitioned.ok) {
|
|
85
|
+
return {
|
|
86
|
+
step: stepName,
|
|
87
|
+
status: STEP_STATUS.INVALID_STATE,
|
|
88
|
+
workerBackend: step.workerBackend,
|
|
89
|
+
input,
|
|
90
|
+
stateDelta: delta,
|
|
91
|
+
usage,
|
|
92
|
+
startedAt,
|
|
93
|
+
finishedAt,
|
|
94
|
+
durationMs,
|
|
95
|
+
error: transitioned.error,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
step: stepName,
|
|
101
|
+
status: STEP_STATUS.DONE,
|
|
102
|
+
workerBackend: step.workerBackend,
|
|
103
|
+
input,
|
|
104
|
+
stateDelta: delta,
|
|
105
|
+
usage,
|
|
106
|
+
startedAt,
|
|
107
|
+
finishedAt,
|
|
108
|
+
durationMs,
|
|
109
|
+
error: null,
|
|
110
|
+
state: transitioned.state,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function createRun(flow, initialState = {}) {
|
|
115
|
+
const seeded = createInitialState(flow.stateSchema, initialState);
|
|
116
|
+
if (!seeded.ok) {
|
|
117
|
+
return {
|
|
118
|
+
flow,
|
|
119
|
+
state: initialState,
|
|
120
|
+
frontier: [],
|
|
121
|
+
completed: new Set(),
|
|
122
|
+
joinProgress: new Map(),
|
|
123
|
+
usage: new Map(),
|
|
124
|
+
history: [],
|
|
125
|
+
status: RUN_STATUS.ERROR,
|
|
126
|
+
error: seeded.error,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
flow,
|
|
131
|
+
state: seeded.state,
|
|
132
|
+
frontier: [flow.startStep],
|
|
133
|
+
completed: new Set(),
|
|
134
|
+
joinProgress: new Map(),
|
|
135
|
+
usage: new Map(),
|
|
136
|
+
history: [],
|
|
137
|
+
status: RUN_STATUS.RUNNING,
|
|
138
|
+
error: null,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function pickReadyStep(run) {
|
|
143
|
+
const order = run.flow.stepOrder;
|
|
144
|
+
let best = null;
|
|
145
|
+
for (const stepName of run.frontier) {
|
|
146
|
+
if (best === null || order.indexOf(stepName) < order.indexOf(best)) best = stepName;
|
|
147
|
+
}
|
|
148
|
+
return best;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function normalizeRouterResult(nextTargets) {
|
|
152
|
+
if (nextTargets === TERMINAL) return { targets: [], invalid: false };
|
|
153
|
+
if (typeof nextTargets === 'string') return { targets: [nextTargets], invalid: false };
|
|
154
|
+
if (Array.isArray(nextTargets)) return { targets: nextTargets.filter((t) => t !== TERMINAL), invalid: false };
|
|
155
|
+
return { targets: [], invalid: true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function advanceRun(run) {
|
|
159
|
+
if (run.status !== RUN_STATUS.RUNNING) return run;
|
|
160
|
+
if (run.frontier.length === 0) return { ...run, status: RUN_STATUS.COMPLETED };
|
|
161
|
+
|
|
162
|
+
const stepName = pickReadyStep(run);
|
|
163
|
+
const frontier = run.frontier.filter((s) => s !== stepName);
|
|
164
|
+
const consumedSoFar = run.usage.get(stepName) || 0;
|
|
165
|
+
|
|
166
|
+
const result = await runStep(run.flow, stepName, run.state, { consumedSoFar });
|
|
167
|
+
const usage = new Map(run.usage);
|
|
168
|
+
if (result.usage) usage.set(stepName, result.usage.total);
|
|
169
|
+
const history = [...run.history, result];
|
|
170
|
+
|
|
171
|
+
if (result.status !== STEP_STATUS.DONE) {
|
|
172
|
+
const status = result.status === STEP_STATUS.BUDGET_EXHAUSTED ? RUN_STATUS.BUDGET_EXHAUSTED : RUN_STATUS.ERROR;
|
|
173
|
+
return { ...run, usage, history, frontier: [], status, error: result.error };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const completed = new Set(run.completed);
|
|
177
|
+
completed.add(stepName);
|
|
178
|
+
|
|
179
|
+
const step = run.flow.steps[stepName];
|
|
180
|
+
const routerResult = step.router(result.state);
|
|
181
|
+
const { targets, invalid } = normalizeRouterResult(routerResult);
|
|
182
|
+
|
|
183
|
+
if (invalid) {
|
|
184
|
+
return {
|
|
185
|
+
...run,
|
|
186
|
+
state: result.state,
|
|
187
|
+
usage,
|
|
188
|
+
history,
|
|
189
|
+
completed,
|
|
190
|
+
frontier: [],
|
|
191
|
+
status: RUN_STATUS.ERROR,
|
|
192
|
+
error: { code: 'ROUTER_INVALID_RESULT', message: `step "${stepName}" router returned an unsupported value` },
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const joinProgress = new Map(run.joinProgress);
|
|
197
|
+
const nextFrontier = [...frontier];
|
|
198
|
+
|
|
199
|
+
for (const target of targets) {
|
|
200
|
+
const targetStep = run.flow.steps[target];
|
|
201
|
+
if (!targetStep) {
|
|
202
|
+
return {
|
|
203
|
+
...run,
|
|
204
|
+
state: result.state,
|
|
205
|
+
usage,
|
|
206
|
+
history,
|
|
207
|
+
completed,
|
|
208
|
+
frontier: [],
|
|
209
|
+
status: RUN_STATUS.ERROR,
|
|
210
|
+
error: { code: 'ROUTER_UNKNOWN_STEP', message: `step "${stepName}" router named unknown step "${target}"` },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (targetStep.waitFor && targetStep.waitFor.mode === 'all') {
|
|
215
|
+
const satisfied = new Set(joinProgress.get(target) || []);
|
|
216
|
+
satisfied.add(stepName);
|
|
217
|
+
joinProgress.set(target, satisfied);
|
|
218
|
+
const ready = targetStep.waitFor.steps.every((predecessor) => satisfied.has(predecessor));
|
|
219
|
+
if (ready && !nextFrontier.includes(target)) nextFrontier.push(target);
|
|
220
|
+
} else if (!nextFrontier.includes(target)) {
|
|
221
|
+
nextFrontier.push(target);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
...run,
|
|
227
|
+
state: result.state,
|
|
228
|
+
usage,
|
|
229
|
+
history,
|
|
230
|
+
completed,
|
|
231
|
+
joinProgress,
|
|
232
|
+
frontier: nextFrontier,
|
|
233
|
+
status: nextFrontier.length === 0 ? RUN_STATUS.COMPLETED : RUN_STATUS.RUNNING,
|
|
234
|
+
error: null,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function runFlow(flow, initialState = {}, { maxSteps = 10000 } = {}) {
|
|
239
|
+
let run = createRun(flow, initialState);
|
|
240
|
+
let steps = 0;
|
|
241
|
+
while (run.status === RUN_STATUS.RUNNING && steps < maxSteps) {
|
|
242
|
+
run = await advanceRun(run);
|
|
243
|
+
steps += 1;
|
|
244
|
+
}
|
|
245
|
+
if (run.status === RUN_STATUS.RUNNING && steps >= maxSteps) {
|
|
246
|
+
return { ...run, status: RUN_STATUS.ERROR, error: { code: 'MAX_STEPS_EXCEEDED', message: `flow exceeded ${maxSteps} steps without reaching a terminal state` } };
|
|
247
|
+
}
|
|
248
|
+
return run;
|
|
249
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/errors.mjs — typed errors for the flow engine.
|
|
3
|
+
*
|
|
4
|
+
* FlowDefinitionError is thrown at flow-load time (defineFlow/loadFlow) so an
|
|
5
|
+
* invalid flow (bad schema, dangling step reference, a mutating fan-out step)
|
|
6
|
+
* never reaches the execution engine. State-transition and step-execution
|
|
7
|
+
* failures are NOT thrown — they are returned as structured results (see
|
|
8
|
+
* state.mjs and engine.mjs) so a caller driving a long-running flow never has
|
|
9
|
+
* to wrap every step in try/catch to keep going.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class FlowDefinitionError extends Error {
|
|
13
|
+
constructor(message, errors = []) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'FlowDefinitionError';
|
|
16
|
+
this.code = 'FLOW_DEFINITION_INVALID';
|
|
17
|
+
this.errors = errors;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/index.mjs — public entry point for the deterministic flow engine.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the flow-definition loader, the schema-validated state
|
|
5
|
+
* transition, the and/or join combinators, the step/run execution surface,
|
|
6
|
+
* and the checkpoint/resume layer as one module so a caller needs a single
|
|
7
|
+
* import path (`lib/flows/index.mjs`) instead of reaching into individual
|
|
8
|
+
* files.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { defineFlow, loadFlow } from './define.mjs';
|
|
12
|
+
export { validateSchema } from './schema.mjs';
|
|
13
|
+
export { createInitialState, transition } from './state.mjs';
|
|
14
|
+
export { andJoin, anyJoin } from './joins.mjs';
|
|
15
|
+
export { runStep, createRun, advanceRun, runFlow } from './engine.mjs';
|
|
16
|
+
export { TERMINAL, WORKER_BACKENDS, STEP_STATUS, RUN_STATUS, JOIN_MODES } from './constants.mjs';
|
|
17
|
+
export { FlowDefinitionError } from './errors.mjs';
|
|
18
|
+
export {
|
|
19
|
+
FlowCheckpointError,
|
|
20
|
+
runsDir,
|
|
21
|
+
checkpointRun,
|
|
22
|
+
loadCheckpoint,
|
|
23
|
+
resumeRun,
|
|
24
|
+
startRun,
|
|
25
|
+
tickCheckpointed,
|
|
26
|
+
runCheckpointed,
|
|
27
|
+
} from './checkpoint.mjs';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/joins.mjs — and/or join combinators for step declarations.
|
|
3
|
+
*
|
|
4
|
+
* andJoin(steps) declares that a step only becomes ready once every listed
|
|
5
|
+
* predecessor has completed and routed into it (a fan-in barrier). anyJoin
|
|
6
|
+
* (steps) declares reconvergence — the step is ready the first time any one
|
|
7
|
+
* of the listed predecessors routes into it, which is also the engine's
|
|
8
|
+
* default when a step declares no waitFor at all. Both return a plain,
|
|
9
|
+
* serializable `waitFor` descriptor consumed by defineFlow() and engine.mjs.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function andJoin(steps) {
|
|
13
|
+
return { mode: 'all', steps: [...steps] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function anyJoin(steps) {
|
|
17
|
+
return { mode: 'any', steps: [...steps] };
|
|
18
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/schema.mjs — hand-rolled JSON-Schema subset validator.
|
|
3
|
+
*
|
|
4
|
+
* Covers the subset flow state schemas need: type (string/number/integer/
|
|
5
|
+
* boolean/object/array/null, or an array of those for a union), properties,
|
|
6
|
+
* required, enum, items, additionalProperties, and minimum/maximum/minLength/
|
|
7
|
+
* maxLength/minItems/maxItems. No $ref, no allOf/anyOf/oneOf, no format
|
|
8
|
+
* keywords — deliberately not a general JSON-Schema implementation, only the
|
|
9
|
+
* smallest validator that lets a flow definition describe a typed state
|
|
10
|
+
* object and reject an invalid transition, per ADR-0001's zero-npm-
|
|
11
|
+
* dependency-core mandate (no ajv/zod).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
function typeOf(value) {
|
|
15
|
+
if (value === null) return 'null';
|
|
16
|
+
if (Array.isArray(value)) return 'array';
|
|
17
|
+
if (Number.isInteger(value)) return 'integer';
|
|
18
|
+
return typeof value;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function matchesType(value, expected) {
|
|
22
|
+
const actual = typeOf(value);
|
|
23
|
+
if (expected === 'number') return actual === 'number' || actual === 'integer';
|
|
24
|
+
if (expected === 'integer') return actual === 'integer';
|
|
25
|
+
return actual === expected;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateNode(schema, value, path, errors) {
|
|
29
|
+
if (!schema || typeof schema !== 'object') return;
|
|
30
|
+
|
|
31
|
+
const expectedTypes = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : null;
|
|
32
|
+
if (expectedTypes && !expectedTypes.some((t) => matchesType(value, t))) {
|
|
33
|
+
errors.push(`${path}: expected type ${expectedTypes.join(' | ')}, got ${typeOf(value)}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (schema.enum && !schema.enum.some((allowed) => deepEqual(allowed, value))) {
|
|
38
|
+
errors.push(`${path}: value not in enum [${schema.enum.map(String).join(', ')}]`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (typeOf(value) === 'object') {
|
|
42
|
+
validateObject(schema, value, path, errors);
|
|
43
|
+
} else if (typeOf(value) === 'array') {
|
|
44
|
+
validateArray(schema, value, path, errors);
|
|
45
|
+
} else if (typeOf(value) === 'number' || typeOf(value) === 'integer') {
|
|
46
|
+
if (schema.minimum !== undefined && value < schema.minimum) errors.push(`${path}: ${value} is below minimum ${schema.minimum}`);
|
|
47
|
+
if (schema.maximum !== undefined && value > schema.maximum) errors.push(`${path}: ${value} is above maximum ${schema.maximum}`);
|
|
48
|
+
} else if (typeOf(value) === 'string') {
|
|
49
|
+
if (schema.minLength !== undefined && value.length < schema.minLength) errors.push(`${path}: length ${value.length} is below minLength ${schema.minLength}`);
|
|
50
|
+
if (schema.maxLength !== undefined && value.length > schema.maxLength) errors.push(`${path}: length ${value.length} is above maxLength ${schema.maxLength}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateObject(schema, value, path, errors) {
|
|
55
|
+
const properties = schema.properties || {};
|
|
56
|
+
for (const key of schema.required || []) {
|
|
57
|
+
if (!(key in value)) errors.push(`${path}: missing required property "${key}"`);
|
|
58
|
+
}
|
|
59
|
+
for (const [key, propValue] of Object.entries(value)) {
|
|
60
|
+
if (properties[key]) {
|
|
61
|
+
validateNode(properties[key], propValue, `${path}.${key}`, errors);
|
|
62
|
+
} else if (schema.additionalProperties === false) {
|
|
63
|
+
errors.push(`${path}: unexpected property "${key}"`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function validateArray(schema, value, path, errors) {
|
|
69
|
+
if (schema.minItems !== undefined && value.length < schema.minItems) errors.push(`${path}: has ${value.length} items, below minItems ${schema.minItems}`);
|
|
70
|
+
if (schema.maxItems !== undefined && value.length > schema.maxItems) errors.push(`${path}: has ${value.length} items, above maxItems ${schema.maxItems}`);
|
|
71
|
+
if (schema.items) {
|
|
72
|
+
value.forEach((item, i) => validateNode(schema.items, item, `${path}[${i}]`, errors));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function deepEqual(a, b) {
|
|
77
|
+
if (a === b) return true;
|
|
78
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
79
|
+
if (typeof a !== 'object') return false;
|
|
80
|
+
const aKeys = Object.keys(a);
|
|
81
|
+
const bKeys = Object.keys(b);
|
|
82
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
83
|
+
return aKeys.every((k) => deepEqual(a[k], b[k]));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function validateSchema(schema, value) {
|
|
87
|
+
const errors = [];
|
|
88
|
+
validateNode(schema, value, '$', errors);
|
|
89
|
+
return { valid: errors.length === 0, errors };
|
|
90
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/flows/state.mjs — typed state transitions validated against a flow's schema.
|
|
3
|
+
*
|
|
4
|
+
* transition() merges a shallow delta into the current state and validates the
|
|
5
|
+
* result against the flow's state schema before it becomes the new state. A
|
|
6
|
+
* failing transition never mutates the caller's state: it returns a structured
|
|
7
|
+
* `{ ok: false, error }` result carrying every schema violation, so the engine
|
|
8
|
+
* can surface a typed `invalid-state` step result instead of throwing mid-flow.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { validateSchema } from './schema.mjs';
|
|
12
|
+
|
|
13
|
+
export function createInitialState(schema, seed = {}) {
|
|
14
|
+
const result = validateSchema(schema, seed);
|
|
15
|
+
if (!result.valid) {
|
|
16
|
+
return { ok: false, error: { code: 'INVALID_INITIAL_STATE', message: 'initial state fails the flow schema', errors: result.errors } };
|
|
17
|
+
}
|
|
18
|
+
return { ok: true, state: seed };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function transition(schema, state, delta = {}) {
|
|
22
|
+
const merged = { ...state, ...delta };
|
|
23
|
+
const result = validateSchema(schema, merged);
|
|
24
|
+
if (!result.valid) {
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
error: { code: 'INVALID_STATE_TRANSITION', message: 'state transition produced invalid state', errors: result.errors },
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return { ok: true, state: merged };
|
|
31
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/frameworks/loader.mjs — pack-precedence framework resolution (ADR-0062, LMCP-F7).
|
|
3
|
+
*
|
|
4
|
+
* A pack manifest's `frameworks` map declares `{ framework-id: relativePath }`,
|
|
5
|
+
* resolved the same way lib/packs/prompts.mjs resolves `prompts`: relative to
|
|
6
|
+
* that pack's own directory (`_packDir`), falling back to `packageRoot` for
|
|
7
|
+
* the core pack (which has no `_packDir`). resolveFramework() walks a
|
|
8
|
+
* tier-precedence-ordered pack list and returns the first pack that declares
|
|
9
|
+
* the id, so a project-tier pack (.cx/packs) overrides the core pack for the
|
|
10
|
+
* same framework id — the same three-tier precedence every other pack asset
|
|
11
|
+
* obeys (ADR-0055).
|
|
12
|
+
*
|
|
13
|
+
* validatePackFrameworks() mirrors validatePackPrompts(): every declared
|
|
14
|
+
* framework file must exist and pass the ADR-0062 schema (schema.mjs), or
|
|
15
|
+
* the pack fails closed with the file path and offending field named.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { existsSync } from 'node:fs';
|
|
19
|
+
import { join as joinPath } from 'node:path';
|
|
20
|
+
import { parseFrameworkFile } from './schema.mjs';
|
|
21
|
+
|
|
22
|
+
function packFrameworkRoot(pack, fallbackRoot) {
|
|
23
|
+
return pack?._packDir || fallbackRoot;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validate every framework file a pack declares.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} pack a manifest object carrying `frameworks: {id: relPath}`
|
|
30
|
+
* @param {object} opts
|
|
31
|
+
* @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
|
|
32
|
+
* @param {Set<string>|string[]} [opts.knownRoles] roles appliesToRole may name
|
|
33
|
+
* @returns {{valid: boolean, errors: string[]}}
|
|
34
|
+
*/
|
|
35
|
+
export function validatePackFrameworks(pack, { packageRoot, knownRoles } = {}) {
|
|
36
|
+
const errors = [];
|
|
37
|
+
const frameworks = pack?.frameworks || {};
|
|
38
|
+
const root = packFrameworkRoot(pack, packageRoot);
|
|
39
|
+
|
|
40
|
+
for (const [frameworkId, relPath] of Object.entries(frameworks)) {
|
|
41
|
+
if (typeof relPath !== 'string' || !relPath) {
|
|
42
|
+
errors.push(`pack '${pack.id}': framework entry for '${frameworkId}' has no file path`);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const absPath = joinPath(root, relPath);
|
|
46
|
+
if (!existsSync(absPath)) {
|
|
47
|
+
errors.push(`pack '${pack.id}': declared framework file missing for '${frameworkId}': ${relPath}`);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const result = parseFrameworkFile(absPath, { knownRoles });
|
|
51
|
+
if (!result.valid) {
|
|
52
|
+
errors.push(...result.errors.map((e) => `pack '${pack.id}': framework '${frameworkId}': ${e}`));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (result.frontmatter.id !== frameworkId) {
|
|
56
|
+
errors.push(
|
|
57
|
+
`pack '${pack.id}': framework '${frameworkId}' declares mismatched frontmatter id '${result.frontmatter.id}'`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { valid: errors.length === 0, errors };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve one framework through the pack registry only, honoring tier precedence.
|
|
67
|
+
*
|
|
68
|
+
* @param {string} frameworkId
|
|
69
|
+
* @param {object} opts
|
|
70
|
+
* @param {object[]} opts.packs pack list, tier-precedence ordered (project before user before builtin)
|
|
71
|
+
* @param {string} opts.packageRoot fallback root for packs with no `_packDir` (e.g. the core pack)
|
|
72
|
+
* @param {Set<string>|string[]} [opts.knownRoles]
|
|
73
|
+
* @returns {{found: true, frontmatter: object, body: string, packId: string} | {found: false}}
|
|
74
|
+
*/
|
|
75
|
+
export function resolveFramework(frameworkId, { packs, packageRoot, knownRoles } = {}) {
|
|
76
|
+
for (const pack of packs || []) {
|
|
77
|
+
const relPath = pack?.frameworks?.[frameworkId];
|
|
78
|
+
if (!relPath) continue;
|
|
79
|
+
const absPath = joinPath(packFrameworkRoot(pack, packageRoot), relPath);
|
|
80
|
+
if (!existsSync(absPath)) continue;
|
|
81
|
+
|
|
82
|
+
const result = parseFrameworkFile(absPath, { knownRoles });
|
|
83
|
+
if (!result.valid) continue;
|
|
84
|
+
|
|
85
|
+
return { found: true, frontmatter: result.frontmatter, body: result.body, packId: pack.id };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { found: false };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* List every framework id a pack declares, without loading file contents.
|
|
93
|
+
*
|
|
94
|
+
* @param {object} pack
|
|
95
|
+
* @returns {string[]}
|
|
96
|
+
*/
|
|
97
|
+
export function listPackFrameworks(pack) {
|
|
98
|
+
return Object.keys(pack?.frameworks || {});
|
|
99
|
+
}
|