@oisincoveney/pipeline 3.24.2 → 4.0.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/README.md +14 -2
- package/defaults/pipeline.yaml +23 -5
- package/defaults/profiles.yaml +104 -21
- package/dist/argo-graph.js +90 -88
- package/dist/argo-submit.d.ts +110 -138
- package/dist/argo-submit.js +363 -378
- package/dist/argo-workflow.d.ts +156 -177
- package/dist/argo-workflow.js +96 -138
- package/dist/backlog.js +5 -4
- package/dist/bench/eval-report.js +21 -12
- package/dist/claude-settings-config.js +23 -31
- package/dist/claude-user-config.js +6 -16
- package/dist/cli/bootstrap-commands.js +3 -3
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.js +176 -171
- package/dist/cli/factory-commands.js +13 -13
- package/dist/cli/format.js +92 -86
- package/dist/cli/loop-commands.js +21 -21
- package/dist/cli/mcp-gateway-commands.js +12 -12
- package/dist/cli/plan-commands.js +85 -116
- package/dist/cli/program.d.ts +3 -3
- package/dist/cli/program.js +41 -43
- package/dist/cli/run-command.d.ts +1 -1
- package/dist/cli/run-command.js +25 -25
- package/dist/cli/run-commands.js +31 -29
- package/dist/cli/run-resolver.d.ts +1 -1
- package/dist/cli/run-resolver.js +30 -32
- package/dist/cli/run-service.d.ts +2 -3
- package/dist/cli/run-service.js +229 -239
- package/dist/cli/submit-options.js +85 -83
- package/dist/cluster-doctor.js +147 -175
- package/dist/codex-config.js +24 -30
- package/dist/commands/bench-command.js +9 -7
- package/dist/commands/coordinator-command.js +35 -0
- package/dist/commands/pipeline-command.js +13 -7
- package/dist/commands/runner-command-command.js +39 -24
- package/dist/commands/runner-node-command.js +31 -0
- package/dist/commands/ticket/complete.js +29 -33
- package/dist/commands/ticket/create.js +84 -82
- package/dist/commands/ticket/graph-check.js +11 -11
- package/dist/commands/ticket/next.js +36 -36
- package/dist/commands/ticket/sequence.js +11 -12
- package/dist/commands/ticket/shared.d.ts +5 -2
- package/dist/commands/ticket/shared.js +63 -66
- package/dist/commands/ticket/start.js +58 -67
- package/dist/commands/ticket-command.js +2 -2
- package/dist/config/defaults.d.ts +139 -140
- package/dist/config/defaults.js +81 -80
- package/dist/config/lint.js +52 -64
- package/dist/config/load.d.ts +4 -5
- package/dist/config/load.js +64 -74
- package/dist/config/schema/catalog.d.ts +2 -1
- package/dist/config/schema/catalog.js +3 -2
- package/dist/config/schema/mcp.js +46 -52
- package/dist/config/schemas.d.ts +382 -528
- package/dist/config/schemas.js +400 -398
- package/dist/config/validate.d.ts +1 -2
- package/dist/config/validate.js +236 -201
- package/dist/config.d.ts +2 -2
- package/dist/context/repo-map.js +105 -119
- package/dist/coordinator/composition.js +169 -0
- package/dist/coordinator/config.js +71 -0
- package/dist/coordinator/run-owner.js +64 -0
- package/dist/coordinator/wave-advance.js +148 -0
- package/dist/coordinator/wave-scheduler.js +50 -0
- package/dist/credentials/broker.d.ts +6 -8
- package/dist/credentials/broker.js +17 -16
- package/dist/credentials/codex-config.js +15 -19
- package/dist/credentials/file-targets.js +9 -15
- package/dist/credentials/local-codex-auth-sync.js +55 -58
- package/dist/credentials/opencode-config.js +28 -32
- package/dist/credentials/runner.js +13 -13
- package/dist/effect-sync-errors.js +28 -0
- package/dist/factory/create-experiment.js +285 -121
- package/dist/factory/exec.js +10 -12
- package/dist/factory/git-credentials.js +4 -4
- package/dist/factory/stamp-answers.js +10 -11
- package/dist/factory/template-update.js +65 -44
- package/dist/factory-lane.d.ts +26 -40
- package/dist/factory-lane.js +26 -25
- package/dist/gates.js +3 -3
- package/dist/git-remote-url.js +17 -19
- package/dist/hooks.d.ts +26 -26
- package/dist/hooks.js +20 -22
- package/dist/index.d.ts +4 -3
- package/dist/index.js +27 -36
- package/dist/install-commands/claude-code.js +100 -92
- package/dist/install-commands/host-selection.js +3 -6
- package/dist/install-commands/opencode.js +264 -259
- package/dist/install-commands/planner.js +163 -181
- package/dist/install-commands/result-format.js +1 -3
- package/dist/install-commands/shared.js +25 -42
- package/dist/install-commands/writer.js +22 -25
- package/dist/install-commands.js +8 -8
- package/dist/jscpd-output.js +29 -0
- package/dist/json-config-merge.js +14 -20
- package/dist/json-line-values.js +17 -6
- package/dist/loop/argo-poll.d.ts +0 -1
- package/dist/loop/argo-poll.js +15 -95
- package/dist/loop/backlog-records.js +1 -3
- package/dist/loop/controller-deps.js +150 -161
- package/dist/loop/controller.js +164 -186
- package/dist/loop/gh-checks.js +62 -61
- package/dist/loop/gh-runner.js +22 -25
- package/dist/loop/loop-command.js +66 -58
- package/dist/loop/loop-controller-entrypoint.js +29 -30
- package/dist/loop/merge.js +36 -40
- package/dist/loop/run-terminal-poll.js +69 -0
- package/dist/mcp/gateway-config.js +25 -29
- package/dist/mcp/gateway-doctor.js +77 -119
- package/dist/mcp/gateway-reconcile.js +17 -22
- package/dist/mcp/gateway-runtime.js +1 -3
- package/dist/mcp/host-config.js +37 -40
- package/dist/mcp/host-renderers.js +10 -18
- package/dist/mcp/repo-local-backends.js +51 -54
- package/dist/mcp/toolhive-vmcp.js +39 -39
- package/dist/model-resolver.js +29 -33
- package/dist/moka-global-config.d.ts +78 -55
- package/dist/moka-global-config.js +93 -92
- package/dist/moka-submit.d.ts +2179 -866
- package/dist/moka-submit.js +97 -99
- package/dist/opencode-project-config.js +34 -40
- package/dist/package-assets.js +1 -3
- package/dist/path-refs.js +2 -2
- package/dist/pipeline-init.js +25 -25
- package/dist/pipeline-runtime.d.ts +15 -14
- package/dist/pipeline-runtime.js +51 -50
- package/dist/planned-node.js +6 -3
- package/dist/planning/compile.d.ts +5 -5
- package/dist/planning/compile.js +140 -139
- package/dist/planning/dependency-refs.js +5 -5
- package/dist/planning/generate.d.ts +1296 -1296
- package/dist/planning/generate.js +307 -314
- package/dist/planning/graph.d.ts +0 -1
- package/dist/planning/graph.js +131 -149
- package/dist/remote/argo/model.d.ts +104 -122
- package/dist/remote/argo/model.js +205 -197
- package/dist/remote/argo/policy.js +39 -47
- package/dist/remote/argo/storage.js +81 -85
- package/dist/remote/jobs/execution-identity.js +70 -0
- package/dist/remote/jobs/job-outcome.js +108 -0
- package/dist/remote/jobs/manifest.js +96 -0
- package/dist/remote/jobs/model.d.ts +158 -0
- package/dist/remote/jobs/model.js +162 -0
- package/dist/remote/jobs/node-bootstrap.js +213 -0
- package/dist/remote/jobs/policy.js +123 -0
- package/dist/remote/jobs/reconcile.js +100 -0
- package/dist/remote/jobs/run-spec-client.js +64 -0
- package/dist/remote/jobs/run-spec-path.js +31 -0
- package/dist/remote/jobs/run-spec-server.d.ts +34 -0
- package/dist/remote/jobs/run-spec-server.js +116 -0
- package/dist/remote/jobs/submit.d.ts +23 -0
- package/dist/remote/jobs/submit.js +116 -0
- package/dist/remote/jobs/watch.js +59 -0
- package/dist/remote/submit/argo-submission.d.ts +3 -1
- package/dist/remote/submit/argo-submission.js +53 -58
- package/dist/remote/submit/compilation.d.ts +2 -0
- package/dist/remote/submit/compilation.js +46 -49
- package/dist/remote/submit/event-boundary.js +62 -76
- package/dist/remote/submit/hook-events.d.ts +6 -0
- package/dist/remote/submit/io.d.ts +7 -1
- package/dist/remote/submit/io.js +40 -43
- package/dist/remote/submit/jobs-submission.d.ts +59 -0
- package/dist/remote/submit/jobs-submission.js +97 -0
- package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
- package/dist/remote/submit/jobs-submit-result.js +34 -0
- package/dist/remote/submit/options.d.ts +49 -0
- package/dist/remote/submit/options.js +27 -0
- package/dist/remote/submit/service.d.ts +40 -1
- package/dist/remote/submit/service.js +67 -49
- package/dist/run-control/command-context.js +6 -8
- package/dist/run-control/commands.js +10 -10
- package/dist/run-control/contracts.d.ts +52 -101
- package/dist/run-control/contracts.js +83 -69
- package/dist/run-control/detach.js +66 -72
- package/dist/run-control/execution-store.js +0 -0
- package/dist/run-control/file-errors.js +1 -3
- package/dist/run-control/logical-segment.js +3 -3
- package/dist/run-control/next-node.js +67 -78
- package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
- package/dist/run-control/resume-command.js +34 -41
- package/dist/run-control/run-artifacts-command.js +84 -105
- package/dist/run-control/run-command-domain.js +7 -11
- package/dist/run-control/run-control-store.js +9 -9
- package/dist/run-control/run-query-command.js +73 -100
- package/dist/run-control/run-record.js +5 -3
- package/dist/run-control/run-state-lock.js +5 -5
- package/dist/run-control/runtime-event-projection.js +38 -50
- package/dist/run-control/runtime-reporter.js +86 -102
- package/dist/run-control/stop-command.js +42 -53
- package/dist/run-control/store-fs-effects.js +3 -1
- package/dist/run-control/store-manifest.js +38 -13
- package/dist/run-control/store-paths.js +3 -3
- package/dist/run-control/store.js +1 -1
- package/dist/run-control/submit-result.js +21 -21
- package/dist/run-control/supervisor.js +78 -68
- package/dist/run-control/workspace.js +2 -2
- package/dist/run-state/git-refs.js +258 -307
- package/dist/runner/opencode-excludes.js +4 -4
- package/dist/runner/subprocess-result.js +28 -37
- package/dist/runner/subprocess.js +41 -41
- package/dist/runner/timeouts.js +14 -13
- package/dist/runner-command/dynamic-command.js +46 -39
- package/dist/runner-command/environment-commands.js +91 -0
- package/dist/runner-command/finalize.js +174 -153
- package/dist/runner-command/lifecycle-context.js +115 -130
- package/dist/runner-command/lifecycle.js +78 -72
- package/dist/runner-command/pre-schedule.js +203 -223
- package/dist/runner-command/run.js +450 -448
- package/dist/runner-command/schedule-source-options.js +8 -10
- package/dist/runner-command/select-ready-wave.js +39 -30
- package/dist/runner-command/task-descriptor.js +11 -17
- package/dist/runner-command-contract.d.ts +147 -157
- package/dist/runner-command-contract.js +403 -411
- package/dist/runner-event-schema.d.ts +364 -505
- package/dist/runner-event-schema.js +187 -175
- package/dist/runner-event-sink.js +37 -43
- package/dist/runner-output.js +6 -6
- package/dist/runner.d.ts +57 -37
- package/dist/runner.js +176 -127
- package/dist/runtime/actor-ids.d.ts +1 -0
- package/dist/runtime/actor-ids.js +2 -2
- package/dist/runtime/agent-node/agent-node.js +99 -110
- package/dist/runtime/agent-node/handoff-finalization.js +23 -24
- package/dist/runtime/agent-node/model-selection.js +19 -22
- package/dist/runtime/agent-node/output-finalization.js +174 -204
- package/dist/runtime/agent-node/prompt-rendering.js +208 -251
- package/dist/runtime/agent-node/session-execution.js +43 -39
- package/dist/runtime/builtins/builtins.js +218 -284
- package/dist/runtime/changed-files/changed-files.js +29 -34
- package/dist/runtime/command-executor/command-executor.js +31 -23
- package/dist/runtime/config-error.js +1 -3
- package/dist/runtime/context/context.js +35 -37
- package/dist/runtime/contracts/contracts.d.ts +3 -0
- package/dist/runtime/contracts/index.d.ts +1 -1
- package/dist/runtime/detached-race.js +11 -14
- package/dist/runtime/drain-merge/drain-merge.js +139 -164
- package/dist/runtime/durable-store/acquisition.js +5 -3
- package/dist/runtime/durable-store/durable-store.d.ts +3 -1
- package/dist/runtime/durable-store/postgres/client.js +29 -0
- package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
- package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
- package/dist/runtime/events/events.js +273 -331
- package/dist/runtime/events/index.js +2 -2
- package/dist/runtime/execution-envelope.js +44 -0
- package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
- package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
- package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
- package/dist/runtime/gates/adjudicator/index.js +2 -2
- package/dist/runtime/gates/contract.js +5 -9
- package/dist/runtime/gates/gates.js +9 -8
- package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
- package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
- package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
- package/dist/runtime/gates/kinds/artifact/index.js +2 -2
- package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
- package/dist/runtime/gates/kinds/builtin/index.js +2 -2
- package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
- package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
- package/dist/runtime/gates/kinds/command/command.js +3 -3
- package/dist/runtime/gates/kinds/command/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
- package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
- package/dist/runtime/gates/kinds/verdict/index.js +2 -2
- package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
- package/dist/runtime/gates/orchestrator.js +72 -86
- package/dist/runtime/gates/registry.js +1 -3
- package/dist/runtime/handoff.d.ts +2 -1
- package/dist/runtime/handoff.js +43 -46
- package/dist/runtime/hooks/command-hook.js +57 -71
- package/dist/runtime/hooks/context.js +21 -25
- package/dist/runtime/hooks/events.js +84 -100
- package/dist/runtime/hooks/execution.js +6 -12
- package/dist/runtime/hooks/hooks.js +34 -23
- package/dist/runtime/hooks/invocation.js +23 -29
- package/dist/runtime/hooks/module-hook.js +36 -35
- package/dist/runtime/hooks/policy.js +32 -29
- package/dist/runtime/hooks/results.js +56 -66
- package/dist/runtime/journal-acquisition.d.ts +3 -2
- package/dist/runtime/journal-acquisition.js +14 -14
- package/dist/runtime/json-validation/json-validation.js +51 -72
- package/dist/runtime/launch-plan-args.js +39 -0
- package/dist/runtime/local-scheduler.js +46 -48
- package/dist/runtime/node-execution.js +376 -445
- package/dist/runtime/node-protocol/node-protocol.js +36 -79
- package/dist/runtime/node-state-store.d.ts +4 -1
- package/dist/runtime/node-state-store.js +13 -57
- package/dist/runtime/node-state-tracker.js +46 -54
- package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
- package/dist/runtime/opencode-adapter.js +23 -24
- package/dist/runtime/opencode-agent-name.js +6 -6
- package/dist/runtime/opencode-runtime.js +115 -93
- package/dist/runtime/opencode-server.js +39 -53
- package/dist/runtime/opencode-session-executor.js +312 -344
- package/dist/runtime/parallel-node/parallel-node.js +123 -134
- package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
- package/dist/runtime/protected-paths/protected-paths.js +70 -71
- package/dist/runtime/remediation/remediation.js +202 -231
- package/dist/runtime/retry.js +8 -10
- package/dist/runtime/run-journal.d.ts +2 -0
- package/dist/runtime/run-journal.js +7 -7
- package/dist/runtime/runtime-results.js +54 -69
- package/dist/runtime/scheduled-dependencies.d.ts +2 -1
- package/dist/runtime/scheduled-dependencies.js +61 -75
- package/dist/runtime/scheduler.js +179 -202
- package/dist/runtime/services/agent-node-runtime-service.js +3 -3
- package/dist/runtime/services/backlog-service.d.ts +1 -2
- package/dist/runtime/services/backlog-service.js +8 -14
- package/dist/runtime/services/command-executor-service.js +1 -1
- package/dist/runtime/services/config-io-service.js +24 -21
- package/dist/runtime/services/drain-merge-git-service.js +1 -1
- package/dist/runtime/services/file-system-service.js +9 -7
- package/dist/runtime/services/git-porcelain-service.js +6 -6
- package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
- package/dist/runtime/services/kubernetes-argo-service.js +90 -84
- package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
- package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
- package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
- package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
- package/dist/runtime/services/kubernetes-lease-service.js +21 -0
- package/dist/runtime/services/mcp-gateway-service.js +143 -94
- package/dist/runtime/services/open-pull-request-git-service.js +1 -3
- package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
- package/dist/runtime/services/opencode-sdk-service.js +7 -7
- package/dist/runtime/services/repo-io-service.js +10 -12
- package/dist/runtime/services/run-journal-file-service.js +2 -2
- package/dist/runtime/services/runner-command-io-service.js +48 -52
- package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
- package/dist/runtime/step/step-node.js +15 -15
- package/dist/runtime/workflow-execution.js +56 -57
- package/dist/runtime/workflow-lifecycle.js +82 -101
- package/dist/runtime/yeet-event-record.js +104 -0
- package/dist/runtime/yeet-executor.js +184 -0
- package/dist/safe-json.js +16 -16
- package/dist/schedule/backlog-context.js +122 -116
- package/dist/schedule/baseline.js +249 -255
- package/dist/schedule/passes/coverage.js +63 -68
- package/dist/schedule/passes/drain-merge.js +34 -40
- package/dist/schedule/passes/ids.js +15 -17
- package/dist/schedule/passes/models.js +40 -37
- package/dist/schedule/passes/open-pull-request.js +16 -20
- package/dist/schedule/passes/references.js +18 -22
- package/dist/schedule/prompts.js +135 -141
- package/dist/schedule/scheduling-roles.js +8 -14
- package/dist/schema-boundary.d.ts +4 -0
- package/dist/schema-boundary.js +145 -0
- package/dist/serialized-write-queue.js +2 -2
- package/dist/standard-output-schemas.js +61 -59
- package/dist/strings.js +12 -12
- package/dist/task-ref.js +5 -5
- package/dist/tickets/apply-ticket-plan.js +52 -61
- package/dist/tickets/backlog-task-store.d.ts +15 -23
- package/dist/tickets/backlog-task-store.js +82 -94
- package/dist/tickets/completion/complete-ticket.js +53 -59
- package/dist/tickets/ticket-graph-dto.d.ts +24 -48
- package/dist/tickets/ticket-graph-dto.js +40 -40
- package/dist/tickets/ticket-graph.d.ts +7 -2
- package/dist/tickets/ticket-graph.js +36 -50
- package/dist/tickets/ticket-plan-command-args.js +6 -10
- package/dist/tickets/ticket-plan-render.js +10 -16
- package/dist/tickets/ticket-plan.d.ts +32 -41
- package/dist/tickets/ticket-plan.js +97 -77
- package/dist/tickets/ticket-selection.d.ts +3 -1
- package/dist/tickets/ticket-selection.js +37 -52
- package/dist/tickets/ticket-task-index.js +13 -17
- package/dist/tickets/validation-error-format.js +3 -10
- package/dist/token-estimator.js +12 -7
- package/dist/unknown-fields.js +21 -0
- package/dist/workflow-submit-contract.d.ts +9 -10
- package/dist/workflow-submit-contract.js +10 -9
- package/docs/adr-moka-postgres-schema-isolation.md +48 -0
- package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
- package/docs/config-architecture.md +13 -13
- package/docs/mcp-host-isolation.md +1 -0
- package/docs/moka-orchestrator-design.md +3 -3
- package/docs/operator-guide.md +1 -1
- package/docs/pipeline-console-runner-contract.md +10 -6
- package/docs/runtime-actor-model.md +18 -18
- package/docs/runtime-test-subsystems.md +200 -0
- package/docs/slash-command-adapter-contract.md +3 -3
- package/package.json +49 -85
- package/dist/config/schema/reference-validation.js +0 -83
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { uniqueGeneratedId } from "../strings.js";
|
|
2
2
|
import { dependentsByNeed, flattenNodes, hasReachableDependent } from "./graph.js";
|
|
3
|
-
import {
|
|
3
|
+
import { parseResultWithSchema, regexString, requiredString, struct } from "../schema-boundary.js";
|
|
4
|
+
import { configIssuesFromSchemaIssues, workflowSchema } from "../config/schemas.js";
|
|
4
5
|
import { validatePipelineConfig } from "../config/validate.js";
|
|
5
6
|
import "../config.js";
|
|
6
7
|
import { ensurePipelineWorkspaceIgnore } from "../run-control/workspace.js";
|
|
@@ -10,8 +11,8 @@ import { createRunnerLaunchPlan } from "../runner.js";
|
|
|
10
11
|
import { normalizeRunnerOutput } from "../runner-output.js";
|
|
11
12
|
import { loadBacklogPlanningContext } from "../schedule/backlog-context.js";
|
|
12
13
|
import { baselineScheduleArtifact } from "../schedule/baseline.js";
|
|
13
|
-
import {
|
|
14
|
-
import { addGeneratedImplementationCoverage } from "../schedule/passes/coverage.js";
|
|
14
|
+
import { isImplementationNode, isWriteCapableParallelChild } from "../schedule/scheduling-roles.js";
|
|
15
|
+
import { addGeneratedImplementationCoverage, hasDownstreamCoverage } from "../schedule/passes/coverage.js";
|
|
15
16
|
import { integrateParallelWriteFanout } from "../schedule/passes/drain-merge.js";
|
|
16
17
|
import { canonicalizeGeneratedScheduleIds } from "../schedule/passes/ids.js";
|
|
17
18
|
import { SCHEDULE_PASS_ORDER } from "../schedule/passes/index.js";
|
|
@@ -19,16 +20,18 @@ import { applyNodeCatalogModelFallbacks } from "../schedule/passes/models.js";
|
|
|
19
20
|
import { appendPullRequestDelivery, shouldAppendPullRequestDelivery } from "../schedule/passes/open-pull-request.js";
|
|
20
21
|
import { namespaceScheduleWorkflows } from "../schedule/passes/references.js";
|
|
21
22
|
import { plannerPrompt, plannerRepairPrompt } from "../schedule/prompts.js";
|
|
23
|
+
import * as Option$1 from "effect/Option";
|
|
24
|
+
import * as Schema from "effect/Schema";
|
|
22
25
|
import { parseDocument, stringify } from "yaml";
|
|
23
|
-
import {
|
|
24
|
-
import { z } from "zod";
|
|
26
|
+
import { isSuccess } from "effect/Result";
|
|
25
27
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
26
28
|
import { join } from "node:path";
|
|
29
|
+
import * as Data from "effect/Data";
|
|
27
30
|
//#region src/planning/generate.ts
|
|
28
31
|
const SCHEDULE_KIND = "pipeline-schedule";
|
|
29
|
-
const ID_RE = /^[a-z][a-z0-9-]
|
|
30
|
-
const SCHEDULE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]
|
|
31
|
-
const MARKDOWN_YAML_FENCE_RE = /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/
|
|
32
|
+
const ID_RE = /^[a-z][a-z0-9-]*$/u;
|
|
33
|
+
const SCHEDULE_ID_RE = /^[A-Za-z0-9][A-Za-z0-9_.-]*$/u;
|
|
34
|
+
const MARKDOWN_YAML_FENCE_RE = /```(?:ya?ml)?\s*\r?\n([\s\S]*?)\r?\n```/iu;
|
|
32
35
|
const SCHEDULE_PLANNER_REPAIR_ATTEMPTS = 1;
|
|
33
36
|
const PREFERRED_IMPLEMENTATION_PROFILE_IDS = ["moka-code-writer"];
|
|
34
37
|
const SCHEDULE_BUILTINS = [
|
|
@@ -42,16 +45,20 @@ const SCHEDULE_BUILTINS = [
|
|
|
42
45
|
"typecheck"
|
|
43
46
|
];
|
|
44
47
|
const PARALLEL_MERGE_BUILTINS = /* @__PURE__ */ new Set(["drain-merge"]);
|
|
45
|
-
const scheduleArtifactSchema =
|
|
46
|
-
generated_at:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
const scheduleArtifactSchema = struct({
|
|
49
|
+
generated_at: requiredString.check(Schema.makeFilter((value) => isSuccess(Schema.decodeUnknownResult(Schema.DateTimeUtcFromString)(value)) || "must be an ISO datetime string", {
|
|
50
|
+
description: "ISO datetime string accepted by Effect DateTime parsing.",
|
|
51
|
+
identifier: "ScheduleArtifactDateTimeString",
|
|
52
|
+
title: "Schedule artifact datetime string"
|
|
53
|
+
})),
|
|
54
|
+
kind: Schema.Literal(SCHEDULE_KIND),
|
|
55
|
+
root_workflow: regexString(ID_RE),
|
|
56
|
+
schedule_id: regexString(SCHEDULE_ID_RE),
|
|
57
|
+
source_entrypoint: regexString(ID_RE),
|
|
58
|
+
task: requiredString,
|
|
59
|
+
version: Schema.Literal(1),
|
|
60
|
+
workflows: Schema.Record(regexString(ID_RE), workflowSchema)
|
|
61
|
+
});
|
|
55
62
|
/**
|
|
56
63
|
* A backlog ticket's frontmatter may declare dependencies on sibling tickets
|
|
57
64
|
* that are not part of this run (e.g. submitting TOVA-766.03 alone when its
|
|
@@ -62,7 +69,7 @@ const scheduleArtifactSchema = z.object({
|
|
|
62
69
|
* the same predicate workUnitDependencyIssues applies after generation — so the
|
|
63
70
|
* planner prompt context and downstream validation stay consistent.
|
|
64
71
|
*/
|
|
65
|
-
|
|
72
|
+
const pruneOutOfScopeDependencies = (context) => {
|
|
66
73
|
const inScope = new Set([...context.parentWorkUnits, ...context.workUnits].map((unit) => unit.id));
|
|
67
74
|
const prune = (unit) => unit.dependencies ? {
|
|
68
75
|
...unit,
|
|
@@ -72,24 +79,27 @@ function pruneOutOfScopeDependencies(context) {
|
|
|
72
79
|
parentWorkUnits: context.parentWorkUnits.map(prune),
|
|
73
80
|
workUnits: context.workUnits.map(prune)
|
|
74
81
|
};
|
|
75
|
-
}
|
|
76
|
-
|
|
82
|
+
};
|
|
83
|
+
var ScheduleArtifactError = class extends Data.TaggedError("ScheduleArtifactError") {
|
|
84
|
+
constructor(message) {
|
|
85
|
+
super({ message });
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const parseScheduleArtifact = (source, sourcePath = "schedule.yaml") => {
|
|
77
89
|
const document = parseDocument(source, {
|
|
78
90
|
prettyErrors: false,
|
|
79
91
|
uniqueKeys: true
|
|
80
92
|
});
|
|
81
93
|
if (document.errors.length > 0) throw new ScheduleArtifactError(`Failed to parse ${sourcePath}: ${document.errors.map((err) => err.message).join("; ")}`);
|
|
82
|
-
const parsed = scheduleArtifactSchema
|
|
83
|
-
if (!parsed.
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
var ScheduleArtifactError = class extends Data.TaggedError("ScheduleArtifactError") {
|
|
87
|
-
constructor(message) {
|
|
88
|
-
super({ message });
|
|
94
|
+
const parsed = parseResultWithSchema(scheduleArtifactSchema, document.toJS(), { onExcessProperty: "error" });
|
|
95
|
+
if (!parsed.ok) {
|
|
96
|
+
const issues = configIssuesFromSchemaIssues(parsed.issues);
|
|
97
|
+
throw new ScheduleArtifactError([`Invalid schedule artifact ${sourcePath}:`, ...issues.map((issue) => issue.path !== void 0 && issue.path !== "" ? `- ${issue.path}: ${issue.message}` : `- ${issue.message}`)].join("\n"));
|
|
89
98
|
}
|
|
99
|
+
return parsed.value;
|
|
90
100
|
};
|
|
91
|
-
|
|
92
|
-
if (!artifact.workflows
|
|
101
|
+
const compileScheduleArtifact = (config, artifact, projectRoot) => {
|
|
102
|
+
if (!Object.hasOwn(artifact.workflows, artifact.root_workflow)) throw new ScheduleArtifactError(`schedule root workflow '${artifact.root_workflow}' is not declared`);
|
|
93
103
|
const { scheduledWorkflows, workflowId } = namespaceScheduleWorkflows(artifact, ScheduleArtifactError);
|
|
94
104
|
const mergedConfig = validatePipelineConfig({
|
|
95
105
|
...structuredClone(config),
|
|
@@ -103,82 +113,40 @@ function compileScheduleArtifact(config, artifact, projectRoot) {
|
|
|
103
113
|
plan: compileWorkflowPlan(mergedConfig, workflowId),
|
|
104
114
|
workflowId
|
|
105
115
|
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
entrypointId: options.entrypointId,
|
|
123
|
-
generatedAt: options.generatedAt ?? /* @__PURE__ */ new Date(),
|
|
124
|
-
runId: options.runId,
|
|
125
|
-
task: options.task
|
|
126
|
-
});
|
|
127
|
-
const planningContext = schedulePlanningContext({
|
|
128
|
-
task: options.task,
|
|
129
|
-
phaseContext: options.phaseContext,
|
|
130
|
-
worktreePath: options.worktreePath
|
|
131
|
-
});
|
|
132
|
-
const generatedArtifact = await planScheduleArtifact(baseline, policy.planner_profile, options, planningContext);
|
|
133
|
-
assertSchedulePassOrder();
|
|
134
|
-
const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, policy.node_catalog, appendPullRequestDelivery(shouldAppendPullRequestDelivery({
|
|
135
|
-
config: options.config,
|
|
136
|
-
requested: options.pullRequestDeliveryRequested
|
|
137
|
-
}), integrateParallelWriteFanout(options.config, addGeneratedImplementationCoverage(options.config, generatedArtifact))))), planningContext);
|
|
138
|
-
validateScheduleArtifact(options.config, artifact, planningContext);
|
|
139
|
-
compileScheduleArtifact(options.config, artifact, options.worktreePath);
|
|
116
|
+
};
|
|
117
|
+
const ticketPlanTaskWorkUnit = (task, localIds = /* @__PURE__ */ new Map()) => ({
|
|
118
|
+
acceptance_criteria: task.acceptance_criteria.map((criterion, index) => ({
|
|
119
|
+
id: `${task.key}-ac-${index + 1}`,
|
|
120
|
+
text: criterion.text
|
|
121
|
+
})),
|
|
122
|
+
dependencies: "depends_on" in task ? task.depends_on.flatMap((key) => {
|
|
123
|
+
const id = localIds.get(key);
|
|
124
|
+
return id === void 0 ? [] : [id];
|
|
125
|
+
}) : [],
|
|
126
|
+
description: task.description,
|
|
127
|
+
id: task.key,
|
|
128
|
+
title: task.title
|
|
129
|
+
});
|
|
130
|
+
const ticketPlanPlanningContext = (plan) => {
|
|
131
|
+
const localIds = new Map(plan.tickets.map((ticket) => [ticket.key, ticket.key]));
|
|
140
132
|
return {
|
|
141
|
-
|
|
142
|
-
|
|
133
|
+
parentWorkUnits: plan.epic ? [ticketPlanTaskWorkUnit(plan.epic)] : [],
|
|
134
|
+
workUnits: plan.tickets.map((ticket) => ticketPlanTaskWorkUnit(ticket, localIds))
|
|
143
135
|
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
function mergePhasePlanningContext(base, phaseContext) {
|
|
149
|
-
const planned = phaseContext?.ticketPlan ? ticketPlanPlanningContext(phaseContext.ticketPlan) : {
|
|
136
|
+
};
|
|
137
|
+
const mergePhasePlanningContext = (base, phaseContext) => {
|
|
138
|
+
const planned = phaseContext?.ticketPlan === void 0 ? {
|
|
150
139
|
parentWorkUnits: [],
|
|
151
140
|
workUnits: []
|
|
152
|
-
};
|
|
141
|
+
} : ticketPlanPlanningContext(phaseContext.ticketPlan);
|
|
153
142
|
return {
|
|
154
143
|
parentWorkUnits: [...base.parentWorkUnits, ...planned.parentWorkUnits],
|
|
155
|
-
...phaseContext?.research ? { research: phaseContext.research }
|
|
144
|
+
...phaseContext?.research === void 0 ? {} : { research: phaseContext.research },
|
|
156
145
|
workUnits: [...base.workUnits, ...planned.workUnits]
|
|
157
146
|
};
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
return {
|
|
162
|
-
parentWorkUnits: plan.epic ? [ticketPlanTaskWorkUnit(plan.epic)] : [],
|
|
163
|
-
workUnits: plan.tickets.map((ticket) => ticketPlanTaskWorkUnit(ticket, localIds))
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
function ticketPlanTaskWorkUnit(task, localIds = /* @__PURE__ */ new Map()) {
|
|
167
|
-
return {
|
|
168
|
-
acceptance_criteria: task.acceptance_criteria.map((criterion, index) => ({
|
|
169
|
-
id: `${task.key}-ac-${index + 1}`,
|
|
170
|
-
text: criterion.text
|
|
171
|
-
})),
|
|
172
|
-
dependencies: "depends_on" in task ? task.depends_on.flatMap((key) => {
|
|
173
|
-
const id = localIds.get(key);
|
|
174
|
-
return id ? [id] : [];
|
|
175
|
-
}) : [],
|
|
176
|
-
description: task.description,
|
|
177
|
-
id: task.key,
|
|
178
|
-
title: task.title
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
function assertSchedulePassOrder() {
|
|
147
|
+
};
|
|
148
|
+
const schedulePlanningContext = (input) => pruneOutOfScopeDependencies(mergePhasePlanningContext(loadBacklogPlanningContext(input.task, input.worktreePath), input.phaseContext));
|
|
149
|
+
const assertSchedulePassOrder = () => {
|
|
182
150
|
if (SCHEDULE_PASS_ORDER.join("\0") !== [
|
|
183
151
|
"coverage",
|
|
184
152
|
"drain-merge",
|
|
@@ -187,61 +155,30 @@ function assertSchedulePassOrder() {
|
|
|
187
155
|
"ids",
|
|
188
156
|
"references"
|
|
189
157
|
].join("\0")) throw new ScheduleArtifactError("Schedule pass order is misconfigured");
|
|
190
|
-
}
|
|
191
|
-
|
|
158
|
+
};
|
|
159
|
+
const persistScheduleArtifact = (worktreePath, artifact) => {
|
|
192
160
|
ensurePipelineWorkspaceIgnore(worktreePath);
|
|
193
161
|
const relativePath = join(".pipeline", "runs", artifact.schedule_id, "schedule.yaml");
|
|
194
162
|
const fullPath = join(worktreePath, relativePath);
|
|
195
163
|
mkdirSync(join(worktreePath, ".pipeline", "runs", artifact.schedule_id), { recursive: true });
|
|
196
164
|
writeFileSync(fullPath, stringify(artifact));
|
|
197
165
|
return relativePath;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (!sourceResult.ok) {
|
|
207
|
-
if (ticketPlanFallback) return ticketPlanFallback;
|
|
208
|
-
throw sourceResult.error;
|
|
209
|
-
}
|
|
210
|
-
const source = requireSchedulePlannerSource(sourceResult.source);
|
|
211
|
-
const initial = acceptedGeneratedSchedule(parseGeneratedSchedule(source, "planner output"));
|
|
212
|
-
if (initial.ok) return initial.artifact;
|
|
213
|
-
return scheduleArtifactAfterRepair(await repairInvalidScheduleArtifact({
|
|
214
|
-
baseline,
|
|
215
|
-
initialFailure: initial.error,
|
|
216
|
-
initialSource: source,
|
|
217
|
-
options,
|
|
218
|
-
plannerProfile: requiredPlannerProfile
|
|
219
|
-
}), ticketPlanFallback ?? baseline, initial.error, source);
|
|
220
|
-
}
|
|
221
|
-
async function runSchedulePlannerSource(plannerProfile, prompt, options) {
|
|
222
|
-
try {
|
|
223
|
-
return {
|
|
224
|
-
ok: true,
|
|
225
|
-
source: await runSchedulePlanner(plannerProfile, prompt, options)
|
|
226
|
-
};
|
|
227
|
-
} catch (err) {
|
|
228
|
-
if (err instanceof ScheduleArtifactError) return {
|
|
229
|
-
error: err,
|
|
230
|
-
ok: false
|
|
231
|
-
};
|
|
232
|
-
throw err;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
function ticketPlanScheduleArtifact(baseline, config, planningContext, phaseContext) {
|
|
236
|
-
if (!phaseContext?.ticketPlan || planningContext.workUnits.length === 0) return;
|
|
166
|
+
};
|
|
167
|
+
const scheduleArtifactPath = (worktreePath, scheduleId) => join(worktreePath, ".pipeline", "runs", scheduleId, "schedule.yaml");
|
|
168
|
+
const profileIdForSchedulingRole = (config, role, preferredIds) => {
|
|
169
|
+
const matchingIds = Object.entries(config.profiles).filter(([, profile]) => profile.scheduling_roles?.includes(role) === true).map(([id]) => id);
|
|
170
|
+
return preferredIds.find((id) => matchingIds.includes(id)) ?? matchingIds.toSorted()[0];
|
|
171
|
+
};
|
|
172
|
+
const ticketPlanScheduleArtifact = (baseline, config, planningContext, phaseContext) => {
|
|
173
|
+
if (phaseContext?.ticketPlan === void 0 || planningContext.workUnits.length === 0) return;
|
|
237
174
|
const implementationProfile = profileIdForSchedulingRole(config, "implementation", PREFERRED_IMPLEMENTATION_PROFILE_IDS);
|
|
238
|
-
if (
|
|
175
|
+
if (implementationProfile === void 0) throw new ScheduleArtifactError("Cannot generate TicketPlan schedule: no profile declares scheduling role 'implementation'");
|
|
239
176
|
const usedIds = /* @__PURE__ */ new Set();
|
|
240
177
|
const nodeIdsByUnit = new Map(planningContext.workUnits.map((unit) => [unit.id, uniqueGeneratedId(`${unit.id}-implement`, usedIds, "work-implement")]));
|
|
241
178
|
const nodes = planningContext.workUnits.map((unit) => {
|
|
242
179
|
const needs = (unit.dependencies ?? []).flatMap((id) => {
|
|
243
180
|
const nodeId = nodeIdsByUnit.get(id);
|
|
244
|
-
return nodeId ? [
|
|
181
|
+
return nodeId === void 0 ? [] : [nodeId];
|
|
245
182
|
});
|
|
246
183
|
return {
|
|
247
184
|
id: nodeIdsByUnit.get(unit.id) ?? "work-implement",
|
|
@@ -259,20 +196,16 @@ function ticketPlanScheduleArtifact(baseline, config, planningContext, phaseCont
|
|
|
259
196
|
nodes
|
|
260
197
|
} }
|
|
261
198
|
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return preferredIds.find((id) => matchingIds.includes(id)) ?? matchingIds.sort()[0];
|
|
266
|
-
}
|
|
267
|
-
function requireSchedulePlannerProfile(plannerProfile, entrypointId) {
|
|
268
|
-
if (plannerProfile) return plannerProfile;
|
|
199
|
+
};
|
|
200
|
+
const requireSchedulePlannerProfile = (plannerProfile, entrypointId) => {
|
|
201
|
+
if (plannerProfile !== void 0 && plannerProfile.length > 0) return plannerProfile;
|
|
269
202
|
throw new ScheduleArtifactError(`schedule '${entrypointId}' requires planner_profile`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
if (source) return source;
|
|
203
|
+
};
|
|
204
|
+
const requireSchedulePlannerSource = (source) => {
|
|
205
|
+
if (source.length > 0) return source;
|
|
273
206
|
throw new ScheduleArtifactError("schedule planner returned empty output");
|
|
274
|
-
}
|
|
275
|
-
|
|
207
|
+
};
|
|
208
|
+
const scheduleArtifactAfterRepair = (repair, baseline, initialFailure, initialSource) => {
|
|
276
209
|
if (repair.kind === "accepted") return repair.artifact;
|
|
277
210
|
if (repair.kind === "fallback") return baseline;
|
|
278
211
|
throw new ScheduleArtifactError([
|
|
@@ -284,71 +217,37 @@ function scheduleArtifactAfterRepair(repair, baseline, initialFailure, initialSo
|
|
|
284
217
|
"Planner repair output:",
|
|
285
218
|
repair.latestSource
|
|
286
219
|
].join("\n"));
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const repairedSource = await runScheduleRepair(input, latestFailure, latestSource, attempt);
|
|
293
|
-
if (!repairedSource) return { kind: "fallback" };
|
|
294
|
-
const repaired = acceptedGeneratedSchedule(parseGeneratedSchedule(repairedSource, "planner repair output"));
|
|
295
|
-
if (repaired.ok) return {
|
|
296
|
-
artifact: repaired.artifact,
|
|
297
|
-
kind: "accepted"
|
|
298
|
-
};
|
|
299
|
-
latestFailure = repaired.error;
|
|
300
|
-
latestSource = repairedSource;
|
|
301
|
-
}
|
|
302
|
-
return {
|
|
303
|
-
kind: "invalid",
|
|
304
|
-
latestFailure,
|
|
305
|
-
latestSource
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
async function runScheduleRepair(input, latestFailure, latestSource, attempt) {
|
|
309
|
-
try {
|
|
310
|
-
return await runSchedulePlanner(input.plannerProfile, plannerRepairPrompt({
|
|
311
|
-
attempt,
|
|
312
|
-
baseline: input.baseline,
|
|
313
|
-
error: latestFailure,
|
|
314
|
-
source: latestSource
|
|
315
|
-
}), input.options, "schedule-plan-repair");
|
|
316
|
-
} catch (err) {
|
|
317
|
-
if (err instanceof ScheduleArtifactError) return;
|
|
318
|
-
throw err;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
function parseGeneratedSchedule(source, sourcePath) {
|
|
220
|
+
};
|
|
221
|
+
const normalizeGeneratedScheduleSource = (source) => {
|
|
222
|
+
return MARKDOWN_YAML_FENCE_RE.exec(source)?.[1] ?? source;
|
|
223
|
+
};
|
|
224
|
+
const parseGeneratedSchedule = (source, sourcePath) => {
|
|
322
225
|
const parseableSource = normalizeGeneratedScheduleSource(source);
|
|
323
226
|
try {
|
|
324
227
|
return {
|
|
325
228
|
artifact: parseScheduleArtifact(parseableSource, sourcePath),
|
|
326
229
|
ok: true
|
|
327
230
|
};
|
|
328
|
-
} catch (
|
|
329
|
-
if (!(
|
|
231
|
+
} catch (error) {
|
|
232
|
+
if (!(error instanceof ScheduleArtifactError)) throw error;
|
|
330
233
|
return {
|
|
331
|
-
error: new ScheduleArtifactError(`${
|
|
234
|
+
error: new ScheduleArtifactError(`${error.message}\nPlanner output:\n${source}`),
|
|
332
235
|
ok: false
|
|
333
236
|
};
|
|
334
237
|
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
ok: false
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
async function runSchedulePlanner(plannerProfile, prompt, options, nodeId = "schedule-plan") {
|
|
238
|
+
};
|
|
239
|
+
const plannerFailureMessage = (plannerProfile, result) => {
|
|
240
|
+
const stderr = result.stderr?.trim() ?? "";
|
|
241
|
+
const stdout = result.stdout.trim();
|
|
242
|
+
const details = [
|
|
243
|
+
result.timedOut === true ? "timed out waiting for scheduler subprocess" : void 0,
|
|
244
|
+
stderr.length > 0 ? `stderr:\n${stderr}` : void 0,
|
|
245
|
+
stdout.length > 0 ? `stdout:\n${stdout}` : void 0
|
|
246
|
+
].filter((value) => value !== void 0);
|
|
247
|
+
const message = `schedule planner '${plannerProfile}' failed with exit ${result.exitCode}`;
|
|
248
|
+
return details.length === 0 ? message : `${message}\n${details.join("\n")}`;
|
|
249
|
+
};
|
|
250
|
+
const runSchedulePlanner = async (plannerProfile, prompt, options, nodeId = "schedule-plan") => {
|
|
352
251
|
const executor = options.executor ?? runLaunchPlan;
|
|
353
252
|
const plan = createRunnerLaunchPlan(options.config, {
|
|
354
253
|
nodeId,
|
|
@@ -359,72 +258,79 @@ async function runSchedulePlanner(plannerProfile, prompt, options, nodeId = "sch
|
|
|
359
258
|
const result = await executor(plan, {});
|
|
360
259
|
if (result.exitCode !== 0) throw new ScheduleArtifactError(plannerFailureMessage(plannerProfile, result));
|
|
361
260
|
return normalizeRunnerOutput(plan, result.stdout).output.trim();
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
261
|
+
};
|
|
262
|
+
const runSchedulePlannerSource = async (plannerProfile, prompt, options) => {
|
|
263
|
+
try {
|
|
264
|
+
return {
|
|
265
|
+
ok: true,
|
|
266
|
+
source: await runSchedulePlanner(plannerProfile, prompt, options)
|
|
267
|
+
};
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (error instanceof ScheduleArtifactError) return {
|
|
270
|
+
error,
|
|
271
|
+
ok: false
|
|
272
|
+
};
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const runScheduleRepair = async (input, latestFailure, latestSource, attempt) => {
|
|
277
|
+
try {
|
|
278
|
+
return await runSchedulePlanner(input.plannerProfile, plannerRepairPrompt({
|
|
279
|
+
attempt,
|
|
280
|
+
baseline: input.baseline,
|
|
281
|
+
error: latestFailure,
|
|
282
|
+
source: latestSource
|
|
283
|
+
}), input.options, "schedule-plan-repair");
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (error instanceof ScheduleArtifactError) return;
|
|
286
|
+
throw error;
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
const registerContainmentEdge = (parent, child, index) => {
|
|
391
290
|
const dependents = index.get(child.id) ?? [];
|
|
392
291
|
dependents.push(parent);
|
|
393
292
|
index.set(child.id, dependents);
|
|
394
|
-
}
|
|
395
|
-
|
|
293
|
+
};
|
|
294
|
+
const addParallelContainmentEdges = (nodes, index) => {
|
|
396
295
|
for (const node of nodes) {
|
|
397
296
|
if (node.kind !== "parallel") continue;
|
|
398
297
|
for (const child of node.nodes) registerContainmentEdge(node, child, index);
|
|
399
298
|
addParallelContainmentEdges(node.nodes, index);
|
|
400
299
|
}
|
|
401
|
-
}
|
|
402
|
-
|
|
300
|
+
};
|
|
301
|
+
const dependentsByNeedWithContainment = (nested, flat) => {
|
|
403
302
|
const index = dependentsByNeed(flat);
|
|
404
303
|
addParallelContainmentEdges(nested, index);
|
|
405
304
|
return index;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
410
|
-
return collectIssues({
|
|
411
|
-
dependentsByNeed: dependentsByNeedWithContainment(workflow.nodes, nodes),
|
|
412
|
-
nodes,
|
|
413
|
-
workflowId
|
|
414
|
-
});
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
function hasDownstreamDrainMerge(nodeId, index) {
|
|
418
|
-
return hasReachableDependent(nodeId, index, (node) => node.kind === "builtin" && PARALLEL_MERGE_BUILTINS.has(node.builtin));
|
|
419
|
-
}
|
|
420
|
-
function generatedRootWorkflowIssues(artifact) {
|
|
305
|
+
};
|
|
306
|
+
const hasDownstreamDrainMerge = (nodeId, index) => hasReachableDependent(nodeId, index, (node) => node.kind === "builtin" && PARALLEL_MERGE_BUILTINS.has(node.builtin));
|
|
307
|
+
const generatedRootWorkflowIssues = (artifact) => {
|
|
421
308
|
const workflowIds = Object.keys(artifact.workflows);
|
|
422
309
|
const issues = [];
|
|
423
310
|
if (artifact.root_workflow !== "root") issues.push("generated schedules must use root_workflow 'root'");
|
|
424
|
-
if (workflowIds.length !== 1 || !artifact.workflows
|
|
311
|
+
if (workflowIds.length !== 1 || !Object.hasOwn(artifact.workflows, "root")) issues.push("generated schedules must embed exactly one task-specific workflow named 'root'");
|
|
425
312
|
return issues;
|
|
426
|
-
}
|
|
427
|
-
|
|
313
|
+
};
|
|
314
|
+
const backlogWorkUnitTaskContext = (unit) => ({
|
|
315
|
+
...unit.acceptance_criteria.length > 0 ? { acceptance_criteria: unit.acceptance_criteria } : {},
|
|
316
|
+
...unit.description !== void 0 && unit.description.length > 0 ? { description: unit.description } : {},
|
|
317
|
+
id: unit.id,
|
|
318
|
+
...unit.title !== void 0 && unit.title.length > 0 ? { title: unit.title } : {}
|
|
319
|
+
});
|
|
320
|
+
const hydrateWorkflowNodeTaskContext = (node, contexts) => {
|
|
321
|
+
const contextId = node.task_context?.id;
|
|
322
|
+
const context = contextId !== void 0 && contextId.length > 0 ? contexts.get(contextId) : void 0;
|
|
323
|
+
const hydrated = context === void 0 ? node : {
|
|
324
|
+
...node,
|
|
325
|
+
task_context: context
|
|
326
|
+
};
|
|
327
|
+
if (hydrated.kind !== "parallel") return hydrated;
|
|
328
|
+
return {
|
|
329
|
+
...hydrated,
|
|
330
|
+
nodes: hydrated.nodes.map((child) => hydrateWorkflowNodeTaskContext(child, contexts))
|
|
331
|
+
};
|
|
332
|
+
};
|
|
333
|
+
const hydrateScheduleTaskContexts = (artifact, planningContext) => {
|
|
428
334
|
const contexts = new Map([...planningContext.parentWorkUnits, ...planningContext.workUnits].map((unit) => [unit.id, backlogWorkUnitTaskContext(unit)]));
|
|
429
335
|
if (contexts.size === 0) return artifact;
|
|
430
336
|
return {
|
|
@@ -434,34 +340,34 @@ function hydrateScheduleTaskContexts(artifact, planningContext) {
|
|
|
434
340
|
nodes: workflow.nodes.map((node) => hydrateWorkflowNodeTaskContext(node, contexts))
|
|
435
341
|
}]))
|
|
436
342
|
};
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
id
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
};
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
return
|
|
463
|
-
}
|
|
464
|
-
|
|
343
|
+
};
|
|
344
|
+
const nodesByAssignedWorkUnit = (nodes) => {
|
|
345
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
346
|
+
for (const node of nodes) {
|
|
347
|
+
const id = node.task_context?.id;
|
|
348
|
+
if (id === void 0 || id.length === 0) continue;
|
|
349
|
+
const current = grouped.get(id) ?? [];
|
|
350
|
+
current.push(node);
|
|
351
|
+
grouped.set(id, current);
|
|
352
|
+
}
|
|
353
|
+
return grouped;
|
|
354
|
+
};
|
|
355
|
+
const flattenWorkflowNodes = (nodes) => flattenNodes(nodes, (node) => node.kind === "parallel" ? node.nodes : void 0);
|
|
356
|
+
const workflowNodeIssues = (artifact, collectIssues) => Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
|
|
357
|
+
const nodes = flattenWorkflowNodes(workflow.nodes);
|
|
358
|
+
return collectIssues({
|
|
359
|
+
dependentsByNeed: dependentsByNeedWithContainment(workflow.nodes, nodes),
|
|
360
|
+
nodes,
|
|
361
|
+
workflowId
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
const implementationCoverageIssues = (config, artifact) => workflowNodeIssues(artifact, ({ dependentsByNeed, nodes, workflowId }) => nodes.filter((node) => isImplementationNode(config, node)).filter((node) => !hasDownstreamCoverage(config, node.id, dependentsByNeed)).map((node) => `implementation node '${workflowId}.${node.id}' is without downstream verification or review`));
|
|
365
|
+
const unsafeParallelWorktreeIssues = (config, artifact) => workflowNodeIssues(artifact, ({ dependentsByNeed, nodes, workflowId }) => nodes.filter((node) => node.kind === "parallel").flatMap((node) => {
|
|
366
|
+
if (node.nodes.filter((child) => isWriteCapableParallelChild(config, child)).length <= 1) return [];
|
|
367
|
+
if (hasDownstreamDrainMerge(node.id, dependentsByNeed)) return [];
|
|
368
|
+
return [`parallel node '${workflowId}.${node.id}' has write-capable children sharing a worktree without isolated worktree roots or drain-merge integration`];
|
|
369
|
+
}));
|
|
370
|
+
const workUnitDependencyIssues = (config, artifact, workUnits) => {
|
|
465
371
|
if (workUnits.length === 0) return [];
|
|
466
372
|
const workUnitIds = new Set(workUnits.map((unit) => unit.id));
|
|
467
373
|
const dependenciesByUnit = new Map(workUnits.map((unit) => [unit.id, (unit.dependencies ?? []).filter((id) => workUnitIds.has(id))]));
|
|
@@ -471,44 +377,131 @@ function workUnitDependencyIssues(config, artifact, workUnits) {
|
|
|
471
377
|
const nodesByWorkUnit = nodesByAssignedWorkUnit(nodes);
|
|
472
378
|
return nodes.filter((node) => isImplementationNode(config, node)).flatMap((node) => {
|
|
473
379
|
const dependentId = node.task_context?.id;
|
|
474
|
-
if (
|
|
380
|
+
if (dependentId === void 0 || dependentId.length === 0) return [];
|
|
475
381
|
return (dependenciesByUnit.get(dependentId) ?? []).flatMap((prerequisiteId) => {
|
|
476
382
|
const prerequisiteNodes = nodesByWorkUnit.get(prerequisiteId) ?? [];
|
|
477
383
|
return prerequisiteNodes.some((source) => hasReachableDependent(source.id, index, (candidate) => candidate.id === node.id)) ? [] : [`work unit dependency edge missing in '${workflowId}': '${dependentId}' node '${node.id}' must depend on prerequisite '${prerequisiteId}' nodes ${prerequisiteNodes.map((prerequisite) => `'${prerequisite.id}'`).join(", ")}`];
|
|
478
384
|
});
|
|
479
385
|
});
|
|
480
386
|
});
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
return grouped;
|
|
492
|
-
}
|
|
493
|
-
function unsupportedGeneratedBuiltinIssues(artifact) {
|
|
387
|
+
};
|
|
388
|
+
const allWorkflowNodes = (workflows) => Object.values(workflows).flatMap((workflow) => flattenWorkflowNodes(workflow.nodes));
|
|
389
|
+
const missingAssignedWorkUnitIssues = (artifact, workUnits) => {
|
|
390
|
+
if (workUnits.length === 0) return [];
|
|
391
|
+
const assigned = new Set(allWorkflowNodes(artifact.workflows).map((node) => node.task_context?.id).filter((id) => id !== void 0 && id.length > 0));
|
|
392
|
+
const missing = workUnits.map((unit) => unit.id).filter((id) => !assigned.has(id));
|
|
393
|
+
return missing.length > 0 ? [`missing assigned backlog work units: ${missing.join(", ")}`] : [];
|
|
394
|
+
};
|
|
395
|
+
const unsupportedGeneratedBuiltinIssues = (artifact) => {
|
|
494
396
|
const allowed = new Set(SCHEDULE_BUILTINS);
|
|
495
397
|
return allWorkflowNodes(artifact.workflows).flatMap((node) => {
|
|
496
398
|
const nodeBuiltinIssues = node.kind === "builtin" && !allowed.has(node.builtin) ? [`unsupported generated builtin '${node.builtin}' on node '${node.id}'. Allowed builtins: ${SCHEDULE_BUILTINS.join(", ")}`] : [];
|
|
497
399
|
const gateBuiltinIssues = (node.gates ?? []).flatMap((gate) => gate.kind === "builtin" && !allowed.has(gate.builtin) ? [`unsupported generated builtin gate '${gate.builtin}' on node '${node.id}' gate '${gate.id}'. Allowed builtins: ${SCHEDULE_BUILTINS.join(", ")}`] : []);
|
|
498
400
|
return [...nodeBuiltinIssues, ...gateBuiltinIssues];
|
|
499
401
|
});
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return
|
|
512
|
-
}
|
|
402
|
+
};
|
|
403
|
+
const generatedBuiltinsSupported = (artifact) => {
|
|
404
|
+
const issues = unsupportedGeneratedBuiltinIssues(artifact);
|
|
405
|
+
return issues.length === 0 ? { ok: true } : {
|
|
406
|
+
error: new ScheduleArtifactError(["Invalid generated schedule:", ...issues.map((issue) => `- ${issue}`)].join("\n")),
|
|
407
|
+
ok: false
|
|
408
|
+
};
|
|
409
|
+
};
|
|
410
|
+
const acceptedGeneratedSchedule = (parsed) => {
|
|
411
|
+
if (!parsed.ok) return parsed;
|
|
412
|
+
const builtinCheck = generatedBuiltinsSupported(parsed.artifact);
|
|
413
|
+
return builtinCheck.ok ? parsed : builtinCheck;
|
|
414
|
+
};
|
|
415
|
+
const repairInvalidScheduleArtifact = async (input) => {
|
|
416
|
+
let latestFailure = input.initialFailure;
|
|
417
|
+
let latestSource = input.initialSource;
|
|
418
|
+
for (let attempt = 1; attempt <= SCHEDULE_PLANNER_REPAIR_ATTEMPTS; attempt += 1) {
|
|
419
|
+
const repairedSource = await runScheduleRepair(input, latestFailure, latestSource, attempt);
|
|
420
|
+
if (repairedSource === void 0 || repairedSource.length === 0) return { kind: "fallback" };
|
|
421
|
+
const repaired = acceptedGeneratedSchedule(parseGeneratedSchedule(repairedSource, "planner repair output"));
|
|
422
|
+
if (repaired.ok) return {
|
|
423
|
+
artifact: repaired.artifact,
|
|
424
|
+
kind: "accepted"
|
|
425
|
+
};
|
|
426
|
+
latestFailure = repaired.error;
|
|
427
|
+
latestSource = repairedSource;
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
kind: "invalid",
|
|
431
|
+
latestFailure,
|
|
432
|
+
latestSource
|
|
433
|
+
};
|
|
434
|
+
};
|
|
435
|
+
const planScheduleArtifact = async (baseline, plannerProfile, options, planningContext) => {
|
|
436
|
+
const requiredPlannerProfile = requireSchedulePlannerProfile(plannerProfile, options.entrypointId);
|
|
437
|
+
const ticketPlanFallback = ticketPlanScheduleArtifact(baseline, options.config, planningContext, options.phaseContext);
|
|
438
|
+
const prompt = plannerPrompt(options.entrypointId, options.task, baseline, options.config, planningContext);
|
|
439
|
+
const sourceResult = await runSchedulePlannerSource(requiredPlannerProfile, prompt, options);
|
|
440
|
+
if (!sourceResult.ok) {
|
|
441
|
+
if (ticketPlanFallback !== void 0) return ticketPlanFallback;
|
|
442
|
+
throw sourceResult.error;
|
|
443
|
+
}
|
|
444
|
+
const source = requireSchedulePlannerSource(sourceResult.source);
|
|
445
|
+
const initial = acceptedGeneratedSchedule(parseGeneratedSchedule(source, "planner output"));
|
|
446
|
+
if (initial.ok) return initial.artifact;
|
|
447
|
+
const repair = await repairInvalidScheduleArtifact({
|
|
448
|
+
baseline,
|
|
449
|
+
initialFailure: initial.error,
|
|
450
|
+
initialSource: source,
|
|
451
|
+
options,
|
|
452
|
+
plannerProfile: requiredPlannerProfile
|
|
453
|
+
});
|
|
454
|
+
return scheduleArtifactAfterRepair(repair, ticketPlanFallback ?? baseline, initial.error, source);
|
|
455
|
+
};
|
|
456
|
+
const validateScheduleArtifact = (config, artifact, planningContext) => {
|
|
457
|
+
const issues = [
|
|
458
|
+
...generatedRootWorkflowIssues(artifact),
|
|
459
|
+
...missingAssignedWorkUnitIssues(artifact, planningContext.workUnits),
|
|
460
|
+
...workUnitDependencyIssues(config, artifact, planningContext.workUnits),
|
|
461
|
+
...unsupportedGeneratedBuiltinIssues(artifact),
|
|
462
|
+
...implementationCoverageIssues(config, artifact),
|
|
463
|
+
...unsafeParallelWorktreeIssues(config, artifact)
|
|
464
|
+
];
|
|
465
|
+
if (issues.length > 0) throw new ScheduleArtifactError(["Invalid generated schedule:", ...issues.map((issue) => `- ${issue}`)].join("\n"));
|
|
466
|
+
};
|
|
467
|
+
const generateScheduleArtifactInMemory = async (options) => {
|
|
468
|
+
if (!Object.hasOwn(options.config.entrypoints, options.entrypointId)) throw new ScheduleArtifactError(`entrypoint '${options.entrypointId}' is not a scheduled entrypoint`);
|
|
469
|
+
const entrypoint = options.config.entrypoints[options.entrypointId];
|
|
470
|
+
if (!("schedule" in entrypoint)) throw new ScheduleArtifactError(`entrypoint '${options.entrypointId}' is not a scheduled entrypoint`);
|
|
471
|
+
if (!Object.hasOwn(options.config.schedules, entrypoint.schedule)) throw new ScheduleArtifactError(`schedule policy '${entrypoint.schedule}' is not declared`);
|
|
472
|
+
const policy = options.config.schedules[entrypoint.schedule];
|
|
473
|
+
const baseline = baselineScheduleArtifact({
|
|
474
|
+
baseline: policy.baseline,
|
|
475
|
+
config: options.config,
|
|
476
|
+
entrypointId: options.entrypointId,
|
|
477
|
+
generatedAt: options.generatedAt ?? /* @__PURE__ */ new Date(),
|
|
478
|
+
runId: options.runId,
|
|
479
|
+
task: options.task
|
|
480
|
+
});
|
|
481
|
+
const planningContext = schedulePlanningContext({
|
|
482
|
+
phaseContext: options.phaseContext,
|
|
483
|
+
task: options.task,
|
|
484
|
+
worktreePath: options.worktreePath
|
|
485
|
+
});
|
|
486
|
+
const generatedArtifact = await planScheduleArtifact(baseline, policy.planner_profile, options, planningContext);
|
|
487
|
+
assertSchedulePassOrder();
|
|
488
|
+
const artifact = hydrateScheduleTaskContexts(canonicalizeGeneratedScheduleIds(applyNodeCatalogModelFallbacks(options.config, Option$1.fromUndefinedOr(policy.node_catalog), appendPullRequestDelivery(shouldAppendPullRequestDelivery({
|
|
489
|
+
config: options.config,
|
|
490
|
+
requested: options.pullRequestDeliveryRequested
|
|
491
|
+
}), integrateParallelWriteFanout(options.config, addGeneratedImplementationCoverage(options.config, generatedArtifact))))), planningContext);
|
|
492
|
+
validateScheduleArtifact(options.config, artifact, planningContext);
|
|
493
|
+
compileScheduleArtifact(options.config, artifact, options.worktreePath);
|
|
494
|
+
return {
|
|
495
|
+
artifact,
|
|
496
|
+
yaml: stringify(artifact)
|
|
497
|
+
};
|
|
498
|
+
};
|
|
499
|
+
const generateScheduleArtifact = async (options) => {
|
|
500
|
+
const result = await generateScheduleArtifactInMemory(options);
|
|
501
|
+
return {
|
|
502
|
+
artifact: result.artifact,
|
|
503
|
+
path: persistScheduleArtifact(options.worktreePath, result.artifact)
|
|
504
|
+
};
|
|
505
|
+
};
|
|
513
506
|
//#endregion
|
|
514
507
|
export { ScheduleArtifactError, compileScheduleArtifact, generateScheduleArtifact, generateScheduleArtifactInMemory, parseScheduleArtifact, pruneOutOfScopeDependencies, scheduleArtifactPath, schedulePlanningContext, ticketPlanPlanningContext };
|