@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.
Files changed (382) hide show
  1. package/README.md +14 -2
  2. package/defaults/pipeline.yaml +23 -5
  3. package/defaults/profiles.yaml +104 -21
  4. package/dist/argo-graph.js +90 -88
  5. package/dist/argo-submit.d.ts +110 -138
  6. package/dist/argo-submit.js +363 -378
  7. package/dist/argo-workflow.d.ts +156 -177
  8. package/dist/argo-workflow.js +96 -138
  9. package/dist/backlog.js +5 -4
  10. package/dist/bench/eval-report.js +21 -12
  11. package/dist/claude-settings-config.js +23 -31
  12. package/dist/claude-user-config.js +6 -16
  13. package/dist/cli/bootstrap-commands.js +3 -3
  14. package/dist/cli/doctor.d.ts +1 -1
  15. package/dist/cli/doctor.js +176 -171
  16. package/dist/cli/factory-commands.js +13 -13
  17. package/dist/cli/format.js +92 -86
  18. package/dist/cli/loop-commands.js +21 -21
  19. package/dist/cli/mcp-gateway-commands.js +12 -12
  20. package/dist/cli/plan-commands.js +85 -116
  21. package/dist/cli/program.d.ts +3 -3
  22. package/dist/cli/program.js +41 -43
  23. package/dist/cli/run-command.d.ts +1 -1
  24. package/dist/cli/run-command.js +25 -25
  25. package/dist/cli/run-commands.js +31 -29
  26. package/dist/cli/run-resolver.d.ts +1 -1
  27. package/dist/cli/run-resolver.js +30 -32
  28. package/dist/cli/run-service.d.ts +2 -3
  29. package/dist/cli/run-service.js +229 -239
  30. package/dist/cli/submit-options.js +85 -83
  31. package/dist/cluster-doctor.js +147 -175
  32. package/dist/codex-config.js +24 -30
  33. package/dist/commands/bench-command.js +9 -7
  34. package/dist/commands/coordinator-command.js +35 -0
  35. package/dist/commands/pipeline-command.js +13 -7
  36. package/dist/commands/runner-command-command.js +39 -24
  37. package/dist/commands/runner-node-command.js +31 -0
  38. package/dist/commands/ticket/complete.js +29 -33
  39. package/dist/commands/ticket/create.js +84 -82
  40. package/dist/commands/ticket/graph-check.js +11 -11
  41. package/dist/commands/ticket/next.js +36 -36
  42. package/dist/commands/ticket/sequence.js +11 -12
  43. package/dist/commands/ticket/shared.d.ts +5 -2
  44. package/dist/commands/ticket/shared.js +63 -66
  45. package/dist/commands/ticket/start.js +58 -67
  46. package/dist/commands/ticket-command.js +2 -2
  47. package/dist/config/defaults.d.ts +139 -140
  48. package/dist/config/defaults.js +81 -80
  49. package/dist/config/lint.js +52 -64
  50. package/dist/config/load.d.ts +4 -5
  51. package/dist/config/load.js +64 -74
  52. package/dist/config/schema/catalog.d.ts +2 -1
  53. package/dist/config/schema/catalog.js +3 -2
  54. package/dist/config/schema/mcp.js +46 -52
  55. package/dist/config/schemas.d.ts +382 -528
  56. package/dist/config/schemas.js +400 -398
  57. package/dist/config/validate.d.ts +1 -2
  58. package/dist/config/validate.js +236 -201
  59. package/dist/config.d.ts +2 -2
  60. package/dist/context/repo-map.js +105 -119
  61. package/dist/coordinator/composition.js +169 -0
  62. package/dist/coordinator/config.js +71 -0
  63. package/dist/coordinator/run-owner.js +64 -0
  64. package/dist/coordinator/wave-advance.js +148 -0
  65. package/dist/coordinator/wave-scheduler.js +50 -0
  66. package/dist/credentials/broker.d.ts +6 -8
  67. package/dist/credentials/broker.js +17 -16
  68. package/dist/credentials/codex-config.js +15 -19
  69. package/dist/credentials/file-targets.js +9 -15
  70. package/dist/credentials/local-codex-auth-sync.js +55 -58
  71. package/dist/credentials/opencode-config.js +28 -32
  72. package/dist/credentials/runner.js +13 -13
  73. package/dist/effect-sync-errors.js +28 -0
  74. package/dist/factory/create-experiment.js +285 -121
  75. package/dist/factory/exec.js +10 -12
  76. package/dist/factory/git-credentials.js +4 -4
  77. package/dist/factory/stamp-answers.js +10 -11
  78. package/dist/factory/template-update.js +65 -44
  79. package/dist/factory-lane.d.ts +26 -40
  80. package/dist/factory-lane.js +26 -25
  81. package/dist/gates.js +3 -3
  82. package/dist/git-remote-url.js +17 -19
  83. package/dist/hooks.d.ts +26 -26
  84. package/dist/hooks.js +20 -22
  85. package/dist/index.d.ts +4 -3
  86. package/dist/index.js +27 -36
  87. package/dist/install-commands/claude-code.js +100 -92
  88. package/dist/install-commands/host-selection.js +3 -6
  89. package/dist/install-commands/opencode.js +264 -259
  90. package/dist/install-commands/planner.js +163 -181
  91. package/dist/install-commands/result-format.js +1 -3
  92. package/dist/install-commands/shared.js +25 -42
  93. package/dist/install-commands/writer.js +22 -25
  94. package/dist/install-commands.js +8 -8
  95. package/dist/jscpd-output.js +29 -0
  96. package/dist/json-config-merge.js +14 -20
  97. package/dist/json-line-values.js +17 -6
  98. package/dist/loop/argo-poll.d.ts +0 -1
  99. package/dist/loop/argo-poll.js +15 -95
  100. package/dist/loop/backlog-records.js +1 -3
  101. package/dist/loop/controller-deps.js +150 -161
  102. package/dist/loop/controller.js +164 -186
  103. package/dist/loop/gh-checks.js +62 -61
  104. package/dist/loop/gh-runner.js +22 -25
  105. package/dist/loop/loop-command.js +66 -58
  106. package/dist/loop/loop-controller-entrypoint.js +29 -30
  107. package/dist/loop/merge.js +36 -40
  108. package/dist/loop/run-terminal-poll.js +69 -0
  109. package/dist/mcp/gateway-config.js +25 -29
  110. package/dist/mcp/gateway-doctor.js +77 -119
  111. package/dist/mcp/gateway-reconcile.js +17 -22
  112. package/dist/mcp/gateway-runtime.js +1 -3
  113. package/dist/mcp/host-config.js +37 -40
  114. package/dist/mcp/host-renderers.js +10 -18
  115. package/dist/mcp/repo-local-backends.js +51 -54
  116. package/dist/mcp/toolhive-vmcp.js +39 -39
  117. package/dist/model-resolver.js +29 -33
  118. package/dist/moka-global-config.d.ts +78 -55
  119. package/dist/moka-global-config.js +93 -92
  120. package/dist/moka-submit.d.ts +2179 -866
  121. package/dist/moka-submit.js +97 -99
  122. package/dist/opencode-project-config.js +34 -40
  123. package/dist/package-assets.js +1 -3
  124. package/dist/path-refs.js +2 -2
  125. package/dist/pipeline-init.js +25 -25
  126. package/dist/pipeline-runtime.d.ts +15 -14
  127. package/dist/pipeline-runtime.js +51 -50
  128. package/dist/planned-node.js +6 -3
  129. package/dist/planning/compile.d.ts +5 -5
  130. package/dist/planning/compile.js +140 -139
  131. package/dist/planning/dependency-refs.js +5 -5
  132. package/dist/planning/generate.d.ts +1296 -1296
  133. package/dist/planning/generate.js +307 -314
  134. package/dist/planning/graph.d.ts +0 -1
  135. package/dist/planning/graph.js +131 -149
  136. package/dist/remote/argo/model.d.ts +104 -122
  137. package/dist/remote/argo/model.js +205 -197
  138. package/dist/remote/argo/policy.js +39 -47
  139. package/dist/remote/argo/storage.js +81 -85
  140. package/dist/remote/jobs/execution-identity.js +70 -0
  141. package/dist/remote/jobs/job-outcome.js +108 -0
  142. package/dist/remote/jobs/manifest.js +96 -0
  143. package/dist/remote/jobs/model.d.ts +158 -0
  144. package/dist/remote/jobs/model.js +162 -0
  145. package/dist/remote/jobs/node-bootstrap.js +213 -0
  146. package/dist/remote/jobs/policy.js +123 -0
  147. package/dist/remote/jobs/reconcile.js +100 -0
  148. package/dist/remote/jobs/run-spec-client.js +64 -0
  149. package/dist/remote/jobs/run-spec-path.js +31 -0
  150. package/dist/remote/jobs/run-spec-server.d.ts +34 -0
  151. package/dist/remote/jobs/run-spec-server.js +116 -0
  152. package/dist/remote/jobs/submit.d.ts +23 -0
  153. package/dist/remote/jobs/submit.js +116 -0
  154. package/dist/remote/jobs/watch.js +59 -0
  155. package/dist/remote/submit/argo-submission.d.ts +3 -1
  156. package/dist/remote/submit/argo-submission.js +53 -58
  157. package/dist/remote/submit/compilation.d.ts +2 -0
  158. package/dist/remote/submit/compilation.js +46 -49
  159. package/dist/remote/submit/event-boundary.js +62 -76
  160. package/dist/remote/submit/hook-events.d.ts +6 -0
  161. package/dist/remote/submit/io.d.ts +7 -1
  162. package/dist/remote/submit/io.js +40 -43
  163. package/dist/remote/submit/jobs-submission.d.ts +59 -0
  164. package/dist/remote/submit/jobs-submission.js +97 -0
  165. package/dist/remote/submit/jobs-submit-result.d.ts +25 -0
  166. package/dist/remote/submit/jobs-submit-result.js +34 -0
  167. package/dist/remote/submit/options.d.ts +49 -0
  168. package/dist/remote/submit/options.js +27 -0
  169. package/dist/remote/submit/service.d.ts +40 -1
  170. package/dist/remote/submit/service.js +67 -49
  171. package/dist/run-control/command-context.js +6 -8
  172. package/dist/run-control/commands.js +10 -10
  173. package/dist/run-control/contracts.d.ts +52 -101
  174. package/dist/run-control/contracts.js +83 -69
  175. package/dist/run-control/detach.js +66 -72
  176. package/dist/run-control/execution-store.js +0 -0
  177. package/dist/run-control/file-errors.js +1 -3
  178. package/dist/run-control/logical-segment.js +3 -3
  179. package/dist/run-control/next-node.js +67 -78
  180. package/dist/run-control/postgres/postgres-run-control-store.js +196 -225
  181. package/dist/run-control/resume-command.js +34 -41
  182. package/dist/run-control/run-artifacts-command.js +84 -105
  183. package/dist/run-control/run-command-domain.js +7 -11
  184. package/dist/run-control/run-control-store.js +9 -9
  185. package/dist/run-control/run-query-command.js +73 -100
  186. package/dist/run-control/run-record.js +5 -3
  187. package/dist/run-control/run-state-lock.js +5 -5
  188. package/dist/run-control/runtime-event-projection.js +38 -50
  189. package/dist/run-control/runtime-reporter.js +86 -102
  190. package/dist/run-control/stop-command.js +42 -53
  191. package/dist/run-control/store-fs-effects.js +3 -1
  192. package/dist/run-control/store-manifest.js +38 -13
  193. package/dist/run-control/store-paths.js +3 -3
  194. package/dist/run-control/store.js +1 -1
  195. package/dist/run-control/submit-result.js +21 -21
  196. package/dist/run-control/supervisor.js +78 -68
  197. package/dist/run-control/workspace.js +2 -2
  198. package/dist/run-state/git-refs.js +258 -307
  199. package/dist/runner/opencode-excludes.js +4 -4
  200. package/dist/runner/subprocess-result.js +28 -37
  201. package/dist/runner/subprocess.js +41 -41
  202. package/dist/runner/timeouts.js +14 -13
  203. package/dist/runner-command/dynamic-command.js +46 -39
  204. package/dist/runner-command/environment-commands.js +91 -0
  205. package/dist/runner-command/finalize.js +174 -153
  206. package/dist/runner-command/lifecycle-context.js +115 -130
  207. package/dist/runner-command/lifecycle.js +78 -72
  208. package/dist/runner-command/pre-schedule.js +203 -223
  209. package/dist/runner-command/run.js +450 -448
  210. package/dist/runner-command/schedule-source-options.js +8 -10
  211. package/dist/runner-command/select-ready-wave.js +39 -30
  212. package/dist/runner-command/task-descriptor.js +11 -17
  213. package/dist/runner-command-contract.d.ts +147 -157
  214. package/dist/runner-command-contract.js +403 -411
  215. package/dist/runner-event-schema.d.ts +364 -505
  216. package/dist/runner-event-schema.js +187 -175
  217. package/dist/runner-event-sink.js +37 -43
  218. package/dist/runner-output.js +6 -6
  219. package/dist/runner.d.ts +57 -37
  220. package/dist/runner.js +176 -127
  221. package/dist/runtime/actor-ids.d.ts +1 -0
  222. package/dist/runtime/actor-ids.js +2 -2
  223. package/dist/runtime/agent-node/agent-node.js +99 -110
  224. package/dist/runtime/agent-node/handoff-finalization.js +23 -24
  225. package/dist/runtime/agent-node/model-selection.js +19 -22
  226. package/dist/runtime/agent-node/output-finalization.js +174 -204
  227. package/dist/runtime/agent-node/prompt-rendering.js +208 -251
  228. package/dist/runtime/agent-node/session-execution.js +43 -39
  229. package/dist/runtime/builtins/builtins.js +218 -284
  230. package/dist/runtime/changed-files/changed-files.js +29 -34
  231. package/dist/runtime/command-executor/command-executor.js +31 -23
  232. package/dist/runtime/config-error.js +1 -3
  233. package/dist/runtime/context/context.js +35 -37
  234. package/dist/runtime/contracts/contracts.d.ts +3 -0
  235. package/dist/runtime/contracts/index.d.ts +1 -1
  236. package/dist/runtime/detached-race.js +11 -14
  237. package/dist/runtime/drain-merge/drain-merge.js +139 -164
  238. package/dist/runtime/durable-store/acquisition.js +5 -3
  239. package/dist/runtime/durable-store/durable-store.d.ts +3 -1
  240. package/dist/runtime/durable-store/postgres/client.js +29 -0
  241. package/dist/runtime/durable-store/postgres/migrate-substrate.js +34 -29
  242. package/dist/runtime/durable-store/postgres/postgres-store.js +18 -20
  243. package/dist/runtime/events/events.js +273 -331
  244. package/dist/runtime/events/index.js +2 -2
  245. package/dist/runtime/execution-envelope.js +44 -0
  246. package/dist/runtime/gates/adjudication/llm-judge.js +21 -24
  247. package/dist/runtime/gates/adjudication/structured-claim.js +15 -16
  248. package/dist/runtime/gates/adjudicator/adjudicator.js +27 -27
  249. package/dist/runtime/gates/adjudicator/index.js +2 -2
  250. package/dist/runtime/gates/contract.js +5 -9
  251. package/dist/runtime/gates/gates.js +9 -8
  252. package/dist/runtime/gates/kinds/acceptance/acceptance.js +68 -71
  253. package/dist/runtime/gates/kinds/acceptance/index.js +2 -2
  254. package/dist/runtime/gates/kinds/artifact/artifact.js +4 -4
  255. package/dist/runtime/gates/kinds/artifact/index.js +2 -2
  256. package/dist/runtime/gates/kinds/builtin/builtin.js +4 -4
  257. package/dist/runtime/gates/kinds/builtin/index.js +2 -2
  258. package/dist/runtime/gates/kinds/changed-files/changed-files.js +31 -35
  259. package/dist/runtime/gates/kinds/changed-files/index.js +2 -2
  260. package/dist/runtime/gates/kinds/command/command.js +3 -3
  261. package/dist/runtime/gates/kinds/command/index.js +2 -2
  262. package/dist/runtime/gates/kinds/json-schema/index.js +2 -2
  263. package/dist/runtime/gates/kinds/json-schema/json-schema.js +7 -6
  264. package/dist/runtime/gates/kinds/verdict/index.js +2 -2
  265. package/dist/runtime/gates/kinds/verdict/verdict.js +3 -3
  266. package/dist/runtime/gates/orchestrator.js +72 -86
  267. package/dist/runtime/gates/registry.js +1 -3
  268. package/dist/runtime/handoff.d.ts +2 -1
  269. package/dist/runtime/handoff.js +43 -46
  270. package/dist/runtime/hooks/command-hook.js +57 -71
  271. package/dist/runtime/hooks/context.js +21 -25
  272. package/dist/runtime/hooks/events.js +84 -100
  273. package/dist/runtime/hooks/execution.js +6 -12
  274. package/dist/runtime/hooks/hooks.js +34 -23
  275. package/dist/runtime/hooks/invocation.js +23 -29
  276. package/dist/runtime/hooks/module-hook.js +36 -35
  277. package/dist/runtime/hooks/policy.js +32 -29
  278. package/dist/runtime/hooks/results.js +56 -66
  279. package/dist/runtime/journal-acquisition.d.ts +3 -2
  280. package/dist/runtime/journal-acquisition.js +14 -14
  281. package/dist/runtime/json-validation/json-validation.js +51 -72
  282. package/dist/runtime/launch-plan-args.js +39 -0
  283. package/dist/runtime/local-scheduler.js +46 -48
  284. package/dist/runtime/node-execution.js +376 -445
  285. package/dist/runtime/node-protocol/node-protocol.js +36 -79
  286. package/dist/runtime/node-state-store.d.ts +4 -1
  287. package/dist/runtime/node-state-store.js +13 -57
  288. package/dist/runtime/node-state-tracker.js +46 -54
  289. package/dist/runtime/open-pull-request/open-pull-request.js +201 -187
  290. package/dist/runtime/opencode-adapter.js +23 -24
  291. package/dist/runtime/opencode-agent-name.js +6 -6
  292. package/dist/runtime/opencode-runtime.js +115 -93
  293. package/dist/runtime/opencode-server.js +39 -53
  294. package/dist/runtime/opencode-session-executor.js +312 -344
  295. package/dist/runtime/parallel-node/parallel-node.js +123 -134
  296. package/dist/runtime/parallel-worktrees/parallel-worktrees.js +90 -73
  297. package/dist/runtime/protected-paths/protected-paths.js +70 -71
  298. package/dist/runtime/remediation/remediation.js +202 -231
  299. package/dist/runtime/retry.js +8 -10
  300. package/dist/runtime/run-journal.d.ts +2 -0
  301. package/dist/runtime/run-journal.js +7 -7
  302. package/dist/runtime/runtime-results.js +54 -69
  303. package/dist/runtime/scheduled-dependencies.d.ts +2 -1
  304. package/dist/runtime/scheduled-dependencies.js +61 -75
  305. package/dist/runtime/scheduler.js +179 -202
  306. package/dist/runtime/services/agent-node-runtime-service.js +3 -3
  307. package/dist/runtime/services/backlog-service.d.ts +1 -2
  308. package/dist/runtime/services/backlog-service.js +8 -14
  309. package/dist/runtime/services/command-executor-service.js +1 -1
  310. package/dist/runtime/services/config-io-service.js +24 -21
  311. package/dist/runtime/services/drain-merge-git-service.js +1 -1
  312. package/dist/runtime/services/file-system-service.js +9 -7
  313. package/dist/runtime/services/git-porcelain-service.js +6 -6
  314. package/dist/runtime/services/kubernetes-argo-service.d.ts +10 -3
  315. package/dist/runtime/services/kubernetes-argo-service.js +90 -84
  316. package/dist/runtime/services/kubernetes-jobs-read-service.js +115 -0
  317. package/dist/runtime/services/kubernetes-jobs-service.d.ts +37 -0
  318. package/dist/runtime/services/kubernetes-jobs-service.js +45 -0
  319. package/dist/runtime/services/kubernetes-jobs-watch-service.js +61 -0
  320. package/dist/runtime/services/kubernetes-lease-service.js +21 -0
  321. package/dist/runtime/services/mcp-gateway-service.js +143 -94
  322. package/dist/runtime/services/open-pull-request-git-service.js +1 -3
  323. package/dist/runtime/services/opencode-runtime-server-service.js +10 -12
  324. package/dist/runtime/services/opencode-sdk-service.js +7 -7
  325. package/dist/runtime/services/repo-io-service.js +10 -12
  326. package/dist/runtime/services/run-journal-file-service.js +2 -2
  327. package/dist/runtime/services/runner-command-io-service.js +48 -52
  328. package/dist/runtime/services/runner-event-sink-http-service.js +32 -39
  329. package/dist/runtime/step/step-node.js +15 -15
  330. package/dist/runtime/workflow-execution.js +56 -57
  331. package/dist/runtime/workflow-lifecycle.js +82 -101
  332. package/dist/runtime/yeet-event-record.js +104 -0
  333. package/dist/runtime/yeet-executor.js +184 -0
  334. package/dist/safe-json.js +16 -16
  335. package/dist/schedule/backlog-context.js +122 -116
  336. package/dist/schedule/baseline.js +249 -255
  337. package/dist/schedule/passes/coverage.js +63 -68
  338. package/dist/schedule/passes/drain-merge.js +34 -40
  339. package/dist/schedule/passes/ids.js +15 -17
  340. package/dist/schedule/passes/models.js +40 -37
  341. package/dist/schedule/passes/open-pull-request.js +16 -20
  342. package/dist/schedule/passes/references.js +18 -22
  343. package/dist/schedule/prompts.js +135 -141
  344. package/dist/schedule/scheduling-roles.js +8 -14
  345. package/dist/schema-boundary.d.ts +4 -0
  346. package/dist/schema-boundary.js +145 -0
  347. package/dist/serialized-write-queue.js +2 -2
  348. package/dist/standard-output-schemas.js +61 -59
  349. package/dist/strings.js +12 -12
  350. package/dist/task-ref.js +5 -5
  351. package/dist/tickets/apply-ticket-plan.js +52 -61
  352. package/dist/tickets/backlog-task-store.d.ts +15 -23
  353. package/dist/tickets/backlog-task-store.js +82 -94
  354. package/dist/tickets/completion/complete-ticket.js +53 -59
  355. package/dist/tickets/ticket-graph-dto.d.ts +24 -48
  356. package/dist/tickets/ticket-graph-dto.js +40 -40
  357. package/dist/tickets/ticket-graph.d.ts +7 -2
  358. package/dist/tickets/ticket-graph.js +36 -50
  359. package/dist/tickets/ticket-plan-command-args.js +6 -10
  360. package/dist/tickets/ticket-plan-render.js +10 -16
  361. package/dist/tickets/ticket-plan.d.ts +32 -41
  362. package/dist/tickets/ticket-plan.js +97 -77
  363. package/dist/tickets/ticket-selection.d.ts +3 -1
  364. package/dist/tickets/ticket-selection.js +37 -52
  365. package/dist/tickets/ticket-task-index.js +13 -17
  366. package/dist/tickets/validation-error-format.js +3 -10
  367. package/dist/token-estimator.js +12 -7
  368. package/dist/unknown-fields.js +21 -0
  369. package/dist/workflow-submit-contract.d.ts +9 -10
  370. package/dist/workflow-submit-contract.js +10 -9
  371. package/docs/adr-moka-postgres-schema-isolation.md +48 -0
  372. package/docs/adr-opencode-first-goal-loop-runtime.md +11 -11
  373. package/docs/config-architecture.md +13 -13
  374. package/docs/mcp-host-isolation.md +1 -0
  375. package/docs/moka-orchestrator-design.md +3 -3
  376. package/docs/operator-guide.md +1 -1
  377. package/docs/pipeline-console-runner-contract.md +10 -6
  378. package/docs/runtime-actor-model.md +18 -18
  379. package/docs/runtime-test-subsystems.md +200 -0
  380. package/docs/slash-command-adapter-contract.md +3 -3
  381. package/package.json +49 -85
  382. 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 { workflowSchema } from "../config/schemas.js";
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 { isCoverageNode, isImplementationNode, isWriteCapableParallelChild } from "../schedule/scheduling-roles.js";
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 { Data } from "effect";
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```/i;
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 = z.object({
46
- generated_at: z.string().datetime(),
47
- kind: z.literal(SCHEDULE_KIND),
48
- root_workflow: z.string().regex(ID_RE),
49
- schedule_id: z.string().regex(SCHEDULE_ID_RE),
50
- source_entrypoint: z.string().regex(ID_RE),
51
- task: z.string().min(1),
52
- version: z.literal(1),
53
- workflows: z.record(z.string().regex(ID_RE), workflowSchema)
54
- }).strict();
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
- function pruneOutOfScopeDependencies(context) {
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
- function parseScheduleArtifact(source, sourcePath = "schedule.yaml") {
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.safeParse(document.toJS());
83
- if (!parsed.success) throw new ScheduleArtifactError([`Invalid schedule artifact ${sourcePath}:`, ...parsed.error.issues.map((issue) => issue.path.length > 0 ? `- ${issue.path.join(".")}: ${issue.message}` : `- ${issue.message}`)].join("\n"));
84
- return parsed.data;
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
- function compileScheduleArtifact(config, artifact, projectRoot) {
92
- if (!artifact.workflows[artifact.root_workflow]) throw new ScheduleArtifactError(`schedule root workflow '${artifact.root_workflow}' is not declared`);
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
- async function generateScheduleArtifact(options) {
108
- const result = await generateScheduleArtifactInMemory(options);
109
- return {
110
- artifact: result.artifact,
111
- path: persistScheduleArtifact(options.worktreePath, result.artifact)
112
- };
113
- }
114
- async function generateScheduleArtifactInMemory(options) {
115
- const entrypoint = options.config.entrypoints[options.entrypointId];
116
- if (!(entrypoint && "schedule" in entrypoint)) throw new ScheduleArtifactError(`entrypoint '${options.entrypointId}' is not a scheduled entrypoint`);
117
- const policy = options.config.schedules[entrypoint.schedule];
118
- if (!policy) throw new ScheduleArtifactError(`schedule policy '${entrypoint.schedule}' is not declared`);
119
- const baseline = baselineScheduleArtifact({
120
- baseline: policy.baseline,
121
- config: options.config,
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
- artifact,
142
- yaml: stringify(artifact)
133
+ parentWorkUnits: plan.epic ? [ticketPlanTaskWorkUnit(plan.epic)] : [],
134
+ workUnits: plan.tickets.map((ticket) => ticketPlanTaskWorkUnit(ticket, localIds))
143
135
  };
144
- }
145
- function schedulePlanningContext(input) {
146
- return pruneOutOfScopeDependencies(mergePhasePlanningContext(loadBacklogPlanningContext(input.task, input.worktreePath), input.phaseContext));
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
- function ticketPlanPlanningContext(plan) {
160
- const localIds = new Map(plan.tickets.map((ticket) => [ticket.key, ticket.key]));
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
- function persistScheduleArtifact(worktreePath, artifact) {
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
- function scheduleArtifactPath(worktreePath, scheduleId) {
200
- return join(worktreePath, ".pipeline", "runs", scheduleId, "schedule.yaml");
201
- }
202
- async function planScheduleArtifact(baseline, plannerProfile, options, planningContext) {
203
- const requiredPlannerProfile = requireSchedulePlannerProfile(plannerProfile, options.entrypointId);
204
- const ticketPlanFallback = ticketPlanScheduleArtifact(baseline, options.config, planningContext, options.phaseContext);
205
- const sourceResult = await runSchedulePlannerSource(requiredPlannerProfile, plannerPrompt(options.entrypointId, options.task, baseline, options.config, planningContext), options);
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 (!implementationProfile) throw new ScheduleArtifactError("Cannot generate TicketPlan schedule: no profile declares scheduling role 'implementation'");
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 ? [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
- function profileIdForSchedulingRole(config, role, preferredIds) {
264
- const matchingIds = Object.entries(config.profiles).filter(([, profile]) => profile.scheduling_roles?.includes(role)).map(([id]) => id);
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
- function requireSchedulePlannerSource(source) {
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
- function scheduleArtifactAfterRepair(repair, baseline, initialFailure, initialSource) {
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
- async function repairInvalidScheduleArtifact(input) {
289
- let latestFailure = input.initialFailure;
290
- let latestSource = input.initialSource;
291
- for (let attempt = 1; attempt <= SCHEDULE_PLANNER_REPAIR_ATTEMPTS; attempt += 1) {
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 (err) {
329
- if (!(err instanceof ScheduleArtifactError)) throw err;
231
+ } catch (error) {
232
+ if (!(error instanceof ScheduleArtifactError)) throw error;
330
233
  return {
331
- error: new ScheduleArtifactError(`${err.message}\nPlanner output:\n${source}`),
234
+ error: new ScheduleArtifactError(`${error.message}\nPlanner output:\n${source}`),
332
235
  ok: false
333
236
  };
334
237
  }
335
- }
336
- function normalizeGeneratedScheduleSource(source) {
337
- return MARKDOWN_YAML_FENCE_RE.exec(source)?.[1] ?? source;
338
- }
339
- function acceptedGeneratedSchedule(parsed) {
340
- if (!parsed.ok) return parsed;
341
- const builtinCheck = generatedBuiltinsSupported(parsed.artifact);
342
- return builtinCheck.ok ? parsed : builtinCheck;
343
- }
344
- function generatedBuiltinsSupported(artifact) {
345
- const issues = unsupportedGeneratedBuiltinIssues(artifact);
346
- return issues.length === 0 ? { ok: true } : {
347
- error: new ScheduleArtifactError(["Invalid generated schedule:", ...issues.map((issue) => `- ${issue}`)].join("\n")),
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
- function plannerFailureMessage(plannerProfile, result) {
364
- const details = [
365
- result.timedOut ? "timed out waiting for scheduler subprocess" : void 0,
366
- result.stderr?.trim() ? `stderr:\n${result.stderr.trim()}` : void 0,
367
- result.stdout.trim() ? `stdout:\n${result.stdout.trim()}` : void 0
368
- ].filter((value) => Boolean(value));
369
- const message = `schedule planner '${plannerProfile}' failed with exit ${result.exitCode}`;
370
- return details.length === 0 ? message : `${message}\n${details.join("\n")}`;
371
- }
372
- function validateScheduleArtifact(config, artifact, planningContext) {
373
- const issues = [
374
- ...generatedRootWorkflowIssues(artifact),
375
- ...missingAssignedWorkUnitIssues(artifact, planningContext.workUnits),
376
- ...workUnitDependencyIssues(config, artifact, planningContext.workUnits),
377
- ...unsupportedGeneratedBuiltinIssues(artifact),
378
- ...implementationCoverageIssues(config, artifact),
379
- ...unsafeParallelWorktreeIssues(config, artifact)
380
- ];
381
- if (issues.length > 0) throw new ScheduleArtifactError(["Invalid generated schedule:", ...issues.map((issue) => `- ${issue}`)].join("\n"));
382
- }
383
- function unsafeParallelWorktreeIssues(config, artifact) {
384
- return workflowNodeIssues(artifact, ({ dependentsByNeed, nodes, workflowId }) => nodes.filter((node) => node.kind === "parallel").flatMap((node) => {
385
- if (node.nodes.filter((child) => isWriteCapableParallelChild(config, child)).length <= 1) return [];
386
- if (hasDownstreamDrainMerge(node.id, dependentsByNeed)) return [];
387
- return [`parallel node '${workflowId}.${node.id}' has write-capable children sharing a worktree without isolated worktree roots or drain-merge integration`];
388
- }));
389
- }
390
- function registerContainmentEdge(parent, child, index) {
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
- function addParallelContainmentEdges(nodes, index) {
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
- function dependentsByNeedWithContainment(nested, flat) {
300
+ };
301
+ const dependentsByNeedWithContainment = (nested, flat) => {
403
302
  const index = dependentsByNeed(flat);
404
303
  addParallelContainmentEdges(nested, index);
405
304
  return index;
406
- }
407
- function workflowNodeIssues(artifact, collectIssues) {
408
- return Object.entries(artifact.workflows).flatMap(([workflowId, workflow]) => {
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.root) issues.push("generated schedules must embed exactly one task-specific workflow named 'root'");
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
- function hydrateScheduleTaskContexts(artifact, planningContext) {
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
- function backlogWorkUnitTaskContext(unit) {
439
- return {
440
- ...unit.acceptance_criteria.length > 0 ? { acceptance_criteria: unit.acceptance_criteria } : {},
441
- ...unit.description ? { description: unit.description } : {},
442
- id: unit.id,
443
- ...unit.title ? { title: unit.title } : {}
444
- };
445
- }
446
- function hydrateWorkflowNodeTaskContext(node, contexts) {
447
- const context = node.task_context?.id ? contexts.get(node.task_context.id) : void 0;
448
- const hydrated = context ? {
449
- ...node,
450
- task_context: context
451
- } : node;
452
- if (hydrated.kind !== "parallel") return hydrated;
453
- return {
454
- ...hydrated,
455
- nodes: hydrated.nodes.map((child) => hydrateWorkflowNodeTaskContext(child, contexts))
456
- };
457
- }
458
- function missingAssignedWorkUnitIssues(artifact, workUnits) {
459
- if (workUnits.length === 0) return [];
460
- const assigned = new Set(allWorkflowNodes(artifact.workflows).map((node) => node.task_context?.id).filter((id) => Boolean(id)));
461
- const missing = workUnits.map((unit) => unit.id).filter((id) => !assigned.has(id));
462
- return missing.length > 0 ? [`missing assigned backlog work units: ${missing.join(", ")}`] : [];
463
- }
464
- function workUnitDependencyIssues(config, artifact, workUnits) {
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 (!dependentId) return [];
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
- function nodesByAssignedWorkUnit(nodes) {
483
- const grouped = /* @__PURE__ */ new Map();
484
- for (const node of nodes) {
485
- const id = node.task_context?.id;
486
- if (!id) continue;
487
- const current = grouped.get(id) ?? [];
488
- current.push(node);
489
- grouped.set(id, current);
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
- function implementationCoverageIssues(config, artifact) {
502
- return 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`));
503
- }
504
- function hasDownstreamCoverage(config, nodeId, index) {
505
- return hasReachableDependent(nodeId, index, (node) => isCoverageNode(config, node));
506
- }
507
- function allWorkflowNodes(workflows) {
508
- return Object.values(workflows).flatMap((workflow) => flattenWorkflowNodes(workflow.nodes));
509
- }
510
- function flattenWorkflowNodes(nodes) {
511
- return flattenNodes(nodes, (node) => node.kind === "parallel" ? node.nodes : void 0);
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 };