@deksden-com/dd-flow-cli 0.9.0-beta.14 → 0.9.0-beta.140

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 (493) hide show
  1. package/CHANGELOG.md +733 -0
  2. package/README.md +143 -1
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +376 -0
  6. package/dist/cli/command-metadata.js +197 -0
  7. package/dist/cli/command-option-descriptions.js +191 -0
  8. package/dist/cli/completion-cli.js +70 -0
  9. package/dist/cli/completion.js +57 -0
  10. package/dist/cli/error-output.js +52 -0
  11. package/dist/cli/executor-onboarding.js +76 -0
  12. package/dist/cli/flows.js +29 -0
  13. package/dist/cli/harnesses.js +84 -0
  14. package/dist/cli/help.js +328 -18
  15. package/dist/cli/hook-ingress.js +78 -0
  16. package/dist/cli/input-preparation.js +165 -0
  17. package/dist/cli/memory-flow-admission.js +44 -0
  18. package/dist/cli/memory-init-invocation.js +78 -0
  19. package/dist/cli/memory-init.js +240 -0
  20. package/dist/cli/memory-storage-admission.js +53 -0
  21. package/dist/cli/models.js +39 -0
  22. package/dist/cli/native-hook-ingress.js +80 -0
  23. package/dist/cli/operator-profiles.js +61 -0
  24. package/dist/cli/output-options.js +35 -0
  25. package/dist/cli/response-file.js +71 -0
  26. package/dist/cli/run-cli.js +1689 -545
  27. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  28. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  29. package/dist/cli/runtime-process.js +153 -0
  30. package/dist/cli/setup.js +149 -0
  31. package/dist/cli.js +71 -2
  32. package/dist/domain/stage-catalog.js +1 -1
  33. package/dist/domain/stage-handler-identities.js +20 -0
  34. package/dist/harness-runtime/DD-ZCODE.md +112 -0
  35. package/dist/harness-runtime/OPENCODE-LICENSE.txt +24 -0
  36. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  37. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  38. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  39. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  40. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  41. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  42. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  43. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  44. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  45. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  46. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  47. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  48. package/dist/harness-runtime/lib/daemon-operations.d.mts +1 -0
  49. package/dist/harness-runtime/lib/daemon-operations.mjs +371 -0
  50. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  51. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +724 -0
  52. package/dist/harness-runtime/lib/dd-agy.mjs +70 -0
  53. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  54. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +196 -0
  55. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  56. package/dist/harness-runtime/lib/dd-codex.mjs +817 -0
  57. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +129 -0
  58. package/dist/harness-runtime/lib/dd-droid.mjs +584 -0
  59. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +498 -0
  60. package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
  61. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +507 -0
  62. package/dist/harness-runtime/lib/dd-opencode.mjs +354 -0
  63. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +750 -0
  64. package/dist/harness-runtime/lib/dd-zcode.mjs +1145 -0
  65. package/dist/harness-runtime/lib/delegation-instructions.d.mts +14 -0
  66. package/dist/harness-runtime/lib/delegation-instructions.mjs +157 -0
  67. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  68. package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
  69. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  70. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  71. package/dist/harness-runtime/lib/lease-renewal.mjs +37 -0
  72. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  73. package/dist/harness-runtime/lib/lifecycle-command.mjs +273 -0
  74. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  75. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  76. package/dist/harness-runtime/lib/managed-daemon.d.mts +13 -0
  77. package/dist/harness-runtime/lib/managed-daemon.mjs +654 -0
  78. package/dist/harness-runtime/lib/managed-flow-env.mjs +15 -0
  79. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  80. package/dist/harness-runtime/lib/native-children.d.mts +16 -0
  81. package/dist/harness-runtime/lib/native-children.mjs +113 -0
  82. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  83. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  84. package/dist/harness-runtime/lib/native-request-proof.d.mts +2 -0
  85. package/dist/harness-runtime/lib/native-request-proof.mjs +65 -0
  86. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  87. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  88. package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
  89. package/dist/harness-runtime/lib/opencode-plugin.mjs +85 -0
  90. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  91. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  92. package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
  93. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  94. package/dist/harness-runtime/lib/operation-progress.mjs +114 -0
  95. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  96. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  97. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  98. package/dist/harness-runtime/lib/provider-overload-policy.d.mts +13 -0
  99. package/dist/harness-runtime/lib/provider-overload-policy.mjs +138 -0
  100. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  101. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  102. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  103. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  104. package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
  105. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  106. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  107. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  108. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-1y4a697q.js +81 -0
  109. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-2ny4vxct.js +92 -0
  110. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-6mptkzbc.js +0 -0
  111. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-7x67cf1q.js +52 -0
  112. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9rqn6x4v.js +16 -0
  113. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-9vdz0zwn.js +1661 -0
  114. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ecs5ftka.js +1466 -0
  115. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-frbwqjmf.js +145 -0
  116. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gne7h7mq.js +50 -0
  117. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-gx9wnkzr.js +105 -0
  118. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-m2eywc8w.js +0 -0
  119. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-nwcpakzx.js +12 -0
  120. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-q4g3afnw.js +623 -0
  121. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-qav6mfdz.js +0 -0
  122. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-sxt88q69.js +12 -0
  123. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-t1xa31vm.js +39 -0
  124. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ve4aws36.js +103 -0
  125. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vmytr4q8.js +4 -0
  126. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vt9jbch8.js +36 -0
  127. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-vyas3vgt.js +0 -0
  128. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-wjyeq25y.js +30 -0
  129. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-xgnj2w4v.js +20 -0
  130. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-y2etg5bs.js +9 -0
  131. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-ydhz1zrh.js +238 -0
  132. package/dist/harness-runtime/vendor/opencode-client/chunks/service-probe-z6qdwcsf.js +62 -0
  133. package/dist/harness-runtime/vendor/opencode-client/contract.d.ts +1 -0
  134. package/dist/harness-runtime/vendor/opencode-client/contract.js +12 -0
  135. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.d.ts +2806 -0
  136. package/dist/harness-runtime/vendor/opencode-client/effect/api/api.js +0 -0
  137. package/dist/harness-runtime/vendor/opencode-client/effect/api.d.ts +4 -0
  138. package/dist/harness-runtime/vendor/opencode-client/effect/api.js +0 -0
  139. package/dist/harness-runtime/vendor/opencode-client/effect/client.d.ts +2530 -0
  140. package/dist/harness-runtime/vendor/opencode-client/effect/client.js +15 -0
  141. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.d.ts +7 -0
  142. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client-error.js +6 -0
  143. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.d.ts +2529 -0
  144. package/dist/harness-runtime/vendor/opencode-client/effect/generated/client.js +8 -0
  145. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.d.ts +2 -0
  146. package/dist/harness-runtime/vendor/opencode-client/effect/generated/index.js +12 -0
  147. package/dist/harness-runtime/vendor/opencode-client/effect/index.d.ts +35 -0
  148. package/dist/harness-runtime/vendor/opencode-client/effect/index.js +75 -0
  149. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.d.ts +20 -0
  150. package/dist/harness-runtime/vendor/opencode-client/effect/rpc.js +11 -0
  151. package/dist/harness-runtime/vendor/opencode-client/effect/service.d.ts +90 -0
  152. package/dist/harness-runtime/vendor/opencode-client/effect/service.js +187 -0
  153. package/dist/harness-runtime/vendor/opencode-client/package.json +84 -0
  154. package/dist/harness-runtime/vendor/opencode-client/promise/api.d.ts +21 -0
  155. package/dist/harness-runtime/vendor/opencode-client/promise/api.js +0 -0
  156. package/dist/harness-runtime/vendor/opencode-client/promise/client.d.ts +252 -0
  157. package/dist/harness-runtime/vendor/opencode-client/promise/client.js +14 -0
  158. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.d.ts +8 -0
  159. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client-error.js +6 -0
  160. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.d.ts +259 -0
  161. package/dist/harness-runtime/vendor/opencode-client/promise/generated/client.js +7 -0
  162. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.d.ts +3 -0
  163. package/dist/harness-runtime/vendor/opencode-client/promise/generated/index.js +79 -0
  164. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.d.ts +8932 -0
  165. package/dist/harness-runtime/vendor/opencode-client/promise/generated/types.js +70 -0
  166. package/dist/harness-runtime/vendor/opencode-client/promise/index.d.ts +6 -0
  167. package/dist/harness-runtime/vendor/opencode-client/promise/index.js +83 -0
  168. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.d.ts +32 -0
  169. package/dist/harness-runtime/vendor/opencode-client/promise/rpc.js +7 -0
  170. package/dist/harness-runtime/vendor/opencode-client/promise/service.d.ts +24 -0
  171. package/dist/harness-runtime/vendor/opencode-client/promise/service.js +187 -0
  172. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.d.ts +12 -0
  173. package/dist/harness-runtime/vendor/opencode-client/pty-handoff.js +14 -0
  174. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.d.ts +20 -0
  175. package/dist/harness-runtime/vendor/opencode-client/rpc-runtime.js +14 -0
  176. package/dist/harness-runtime/vendor/opencode-client/service-contender.d.ts +37 -0
  177. package/dist/harness-runtime/vendor/opencode-client/service-contender.js +10 -0
  178. package/dist/harness-runtime/vendor/opencode-client/service-probe.d.ts +70 -0
  179. package/dist/harness-runtime/vendor/opencode-client/service-probe.js +16 -0
  180. package/dist/harness-runtime/vendor/opencode-client/service-timing.d.ts +12 -0
  181. package/dist/harness-runtime/vendor/opencode-client/service-timing.js +10 -0
  182. package/dist/harness-runtime/vendor/opencode-client/service-version.d.ts +2 -0
  183. package/dist/harness-runtime/vendor/opencode-client/service-version.js +6 -0
  184. package/dist/harness-runtime/vendor/opencode-client/service.d.ts +52 -0
  185. package/dist/harness-runtime/vendor/opencode-client/service.js +1 -0
  186. package/dist/harness-runtime/vendor/opencode-client/shared-events.d.ts +11 -0
  187. package/dist/harness-runtime/vendor/opencode-client/shared-events.js +8 -0
  188. package/dist/harness-runtime/vendor/opencode-client/solid/connection.d.ts +37 -0
  189. package/dist/harness-runtime/vendor/opencode-client/solid/connection.js +10 -0
  190. package/dist/harness-runtime/vendor/opencode-client/solid/data.d.ts +211 -0
  191. package/dist/harness-runtime/vendor/opencode-client/solid/data.js +18 -0
  192. package/dist/harness-runtime/vendor/opencode-client/solid/index.d.ts +3 -0
  193. package/dist/harness-runtime/vendor/opencode-client/solid/index.js +32 -0
  194. package/dist/harness-runtime/vendor/opencode-client/solid/pty.d.ts +22 -0
  195. package/dist/harness-runtime/vendor/opencode-client/solid/pty.js +8 -0
  196. package/dist/harness-runtime/vendor/opencode-plugin.mjs +3 -0
  197. package/dist/protocol/local-files.js +18 -9
  198. package/dist/runtime/context.js +8 -4
  199. package/dist/schemas/agent-profile.schema.json +1 -1
  200. package/dist/schemas/code-review-result.schema.json +1 -1
  201. package/dist/schemas/code-verification.schema.json +7 -5
  202. package/dist/schemas/code-work-batch.schema.json +2 -2
  203. package/dist/schemas/discovery-result.schema.json +184 -0
  204. package/dist/schemas/execution-bundle.schema.json +205 -0
  205. package/dist/schemas/execution-settings.schema.json +162 -0
  206. package/dist/schemas/flow-descriptor.schema.json +54 -0
  207. package/dist/schemas/flow-run.schema.json +28 -0
  208. package/dist/schemas/harness-catalog.schema.json +80 -0
  209. package/dist/schemas/mb-init-compose-result.schema.json +457 -0
  210. package/dist/schemas/mb-init-discover-result.schema.json +500 -0
  211. package/dist/schemas/mb-init-finish-result.schema.json +422 -0
  212. package/dist/schemas/mb-init-preflight-result.schema.json +367 -0
  213. package/dist/schemas/mb-init-review-result.schema.json +396 -0
  214. package/dist/schemas/mb-init-verify-result.schema.json +391 -0
  215. package/dist/schemas/memory-bootstrap-receipt-v2.schema.json +67 -0
  216. package/dist/schemas/memory-bootstrap-receipt.schema.json +354 -0
  217. package/dist/schemas/memory-composition-work-result.schema.json +38 -0
  218. package/dist/schemas/memory-repair-work-result.schema.json +49 -0
  219. package/dist/schemas/memory-review-correction-plan.schema.json +30 -0
  220. package/dist/schemas/memory-review-work-result.schema.json +43 -0
  221. package/dist/schemas/model-catalog.schema.json +153 -0
  222. package/dist/schemas/plan-review-decision.schema.json +3 -3
  223. package/dist/schemas/plan-review-result.schema.json +2 -2
  224. package/dist/schemas/profile-settings.schema.json +122 -0
  225. package/dist/schemas/release-manifest.schema.json +291 -0
  226. package/dist/schemas/run-control-receipt.schema.json +118 -0
  227. package/dist/schemas/run-control-request.schema.json +36 -0
  228. package/dist/schemas/run-engine-binding.schema.json +1 -1
  229. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  230. package/dist/services/branch-context.js +3 -3
  231. package/dist/services/canon.js +11 -8
  232. package/dist/services/check-runner-evidence.js +168 -0
  233. package/dist/services/cleanup.js +133 -40
  234. package/dist/services/cli-operation-classifier.js +25 -62
  235. package/dist/services/cli-upgrade-allowlist.js +27 -0
  236. package/dist/services/code-checks.js +1156 -107
  237. package/dist/services/codex-hook-delivery.js +32 -0
  238. package/dist/services/command-context.js +80 -0
  239. package/dist/services/compatibility-preflight.js +13 -3
  240. package/dist/services/config.js +11 -8
  241. package/dist/services/continuation-outcome.js +20 -0
  242. package/dist/services/controller-child-capacity.js +13 -0
  243. package/dist/services/controller-child-observer.js +115 -0
  244. package/dist/services/controller-execution.js +88 -0
  245. package/dist/services/controller-fanout.js +202 -0
  246. package/dist/services/dashboard.js +143 -58
  247. package/dist/services/descriptor-coordinator-session.js +35 -0
  248. package/dist/services/descriptor-root-work.js +53 -0
  249. package/dist/services/descriptor-run-settlement.js +105 -0
  250. package/dist/services/descriptor-run-start.js +449 -0
  251. package/dist/services/descriptor-stage-coordinator.js +197 -0
  252. package/dist/services/descriptor-stage-settlement.js +222 -0
  253. package/dist/services/descriptor-stage-start.js +139 -0
  254. package/dist/services/discovery/agy-discovery.js +2 -0
  255. package/dist/services/discovery/codex-discovery.js +2 -0
  256. package/dist/services/discovery/droid-discovery.js +2 -0
  257. package/dist/services/discovery/grok-discovery.js +2 -0
  258. package/dist/services/discovery/opencode-discovery.js +2 -0
  259. package/dist/services/discovery/static.js +31 -0
  260. package/dist/services/discovery/zcode-discovery.js +2 -0
  261. package/dist/services/engines.js +294 -153
  262. package/dist/services/eval-snapshots.js +1215 -116
  263. package/dist/services/execution-bundles.js +263 -0
  264. package/dist/services/execution-policy.js +185 -0
  265. package/dist/services/executor-onboarding.js +56 -0
  266. package/dist/services/external-work-launch.js +308 -0
  267. package/dist/services/flow-catalog.js +21 -0
  268. package/dist/services/flow-descriptors.js +82 -0
  269. package/dist/services/frozen-engine.js +250 -0
  270. package/dist/services/generic-execution.js +164 -0
  271. package/dist/services/harness-adapter.js +355 -36
  272. package/dist/services/harness-config.js +2 -0
  273. package/dist/services/harness-discovery.js +29 -0
  274. package/dist/services/harness-doctor.js +75 -0
  275. package/dist/services/harness-probe-effects.js +51 -0
  276. package/dist/services/harness-version-probe.js +100 -0
  277. package/dist/services/hooks.js +771 -325
  278. package/dist/services/hybrid-contracts.js +483 -0
  279. package/dist/services/ids.js +29 -1
  280. package/dist/services/installation-lock.js +95 -0
  281. package/dist/services/installed-flow-source.js +183 -0
  282. package/dist/services/lanes.js +78 -65
  283. package/dist/services/lifecycle-command.js +1 -288
  284. package/dist/services/lifecycle-contract.js +1 -0
  285. package/dist/services/lifecycle-invocations.js +2007 -0
  286. package/dist/services/managed-daemon-binding.js +38 -0
  287. package/dist/services/managed-lease-monitor.js +133 -0
  288. package/dist/services/managed-processes.js +475 -55
  289. package/dist/services/memory-accepted-composition.js +77 -0
  290. package/dist/services/memory-baseline-preservation.js +65 -0
  291. package/dist/services/memory-completed-correction-wave.js +320 -0
  292. package/dist/services/memory-compose-handler.js +258 -0
  293. package/dist/services/memory-composition-acceptance.js +199 -0
  294. package/dist/services/memory-composition-graph.js +134 -0
  295. package/dist/services/memory-composition-registration.js +146 -0
  296. package/dist/services/memory-composition-work-custody.js +151 -0
  297. package/dist/services/memory-composition-work-results.js +96 -0
  298. package/dist/services/memory-corrected-review-acceptance.js +129 -0
  299. package/dist/services/memory-corrected-review-batch.js +30 -0
  300. package/dist/services/memory-corrected-review-findings.js +106 -0
  301. package/dist/services/memory-corrected-review-inputs.js +210 -0
  302. package/dist/services/memory-corrected-review-projection.js +78 -0
  303. package/dist/services/memory-corrected-review-receipt.js +170 -0
  304. package/dist/services/memory-corrected-review-registration.js +191 -0
  305. package/dist/services/memory-correction-check-receipt.js +219 -0
  306. package/dist/services/memory-correction-input-owner.js +6 -0
  307. package/dist/services/memory-discover-handler.js +104 -0
  308. package/dist/services/memory-finish-evidence.js +435 -0
  309. package/dist/services/memory-finish-handler.js +215 -0
  310. package/dist/services/memory-finish-integration.js +280 -0
  311. package/dist/services/memory-finish-readiness.js +122 -0
  312. package/dist/services/memory-git-integration.js +151 -0
  313. package/dist/services/memory-historical-corrected-review.js +206 -0
  314. package/dist/services/memory-historical-original-review.js +130 -0
  315. package/dist/services/memory-historical-repair-registration.js +99 -0
  316. package/dist/services/memory-historical-second-review.js +128 -0
  317. package/dist/services/memory-init-bootstrap-readiness.js +210 -0
  318. package/dist/services/memory-init-contexts.js +89 -0
  319. package/dist/services/memory-init-controller-start.js +84 -0
  320. package/dist/services/memory-init-engine-release.js +223 -0
  321. package/dist/services/memory-init-git-policy.js +136 -0
  322. package/dist/services/memory-init-lint-capability.js +185 -0
  323. package/dist/services/memory-init-native-capacity.js +296 -0
  324. package/dist/services/memory-init-preflight.js +330 -0
  325. package/dist/services/memory-init-project-bootstrap.js +599 -0
  326. package/dist/services/memory-init-request-candidate.js +157 -0
  327. package/dist/services/memory-init-request-input.js +66 -0
  328. package/dist/services/memory-init-resource-storage.js +125 -0
  329. package/dist/services/memory-init-run-allocation.js +81 -0
  330. package/dist/services/memory-init-run-bundle.js +98 -0
  331. package/dist/services/memory-init-start.js +181 -0
  332. package/dist/services/memory-init-startup-custody.js +80 -0
  333. package/dist/services/memory-init-startup-input.js +240 -0
  334. package/dist/services/memory-init-storage.js +81 -0
  335. package/dist/services/memory-init-stores.js +359 -0
  336. package/dist/services/memory-init-worktrunk-capability.js +178 -0
  337. package/dist/services/memory-layer-publication.js +611 -0
  338. package/dist/services/memory-layer.js +156 -0
  339. package/dist/services/memory-lint-config.js +85 -0
  340. package/dist/services/memory-native-review-copy.js +165 -0
  341. package/dist/services/memory-normal-flow-entry.js +75 -0
  342. package/dist/services/memory-ordinary-defaults.js +112 -0
  343. package/dist/services/memory-preflight-bootstrap-custody.js +115 -0
  344. package/dist/services/memory-preflight-handler.js +259 -0
  345. package/dist/services/memory-repair-current-view.js +299 -0
  346. package/dist/services/memory-repair-document-delta.js +451 -0
  347. package/dist/services/memory-repair-history.js +491 -0
  348. package/dist/services/memory-repair-physical-binding.js +312 -0
  349. package/dist/services/memory-repair-publication-files.js +152 -0
  350. package/dist/services/memory-repair-publication-intent.js +27 -0
  351. package/dist/services/memory-repair-publication-plan.js +160 -0
  352. package/dist/services/memory-repair-publication-source.js +225 -0
  353. package/dist/services/memory-repair-publication.js +794 -0
  354. package/dist/services/memory-repair-registration.js +239 -0
  355. package/dist/services/memory-repair-work-baseline.js +413 -0
  356. package/dist/services/memory-repair-work-completion.js +97 -0
  357. package/dist/services/memory-repair-work-custody.js +73 -0
  358. package/dist/services/memory-repair-work-results.js +89 -0
  359. package/dist/services/memory-repair-workspace.js +252 -0
  360. package/dist/services/memory-repair-writer-copy.js +735 -0
  361. package/dist/services/memory-result-binding.js +173 -0
  362. package/dist/services/memory-retained-review-authority.js +129 -0
  363. package/dist/services/memory-retained-review-results.js +70 -0
  364. package/dist/services/memory-retained-second-baseline.js +102 -0
  365. package/dist/services/memory-retained-second-repairs.js +156 -0
  366. package/dist/services/memory-review-acceptance.js +214 -0
  367. package/dist/services/memory-review-correction-plan.js +171 -0
  368. package/dist/services/memory-review-handler.js +585 -0
  369. package/dist/services/memory-review-inputs.js +200 -0
  370. package/dist/services/memory-review-receipt.js +180 -0
  371. package/dist/services/memory-review-registration.js +108 -0
  372. package/dist/services/memory-review-work-custody.js +124 -0
  373. package/dist/services/memory-review-work-input.js +72 -0
  374. package/dist/services/memory-review-work-results.js +73 -0
  375. package/dist/services/memory-second-corrected-review-inputs.js +165 -0
  376. package/dist/services/memory-second-correction-plan.js +76 -0
  377. package/dist/services/memory-second-repair-baseline.js +91 -0
  378. package/dist/services/memory-second-repair-launch.js +614 -0
  379. package/dist/services/memory-second-repair-registration.js +88 -0
  380. package/dist/services/memory-second-repair-source.js +187 -0
  381. package/dist/services/memory-second-review-acceptance.js +115 -0
  382. package/dist/services/memory-source-observations.js +117 -0
  383. package/dist/services/memory-stage-dispatch.js +28 -0
  384. package/dist/services/memory-stage-results.js +199 -0
  385. package/dist/services/memory-verification-acceptance.js +135 -0
  386. package/dist/services/memory-verification-checks.js +252 -0
  387. package/dist/services/memory-verification-receipt.js +159 -0
  388. package/dist/services/memory-verify-handler.js +135 -0
  389. package/dist/services/memory-work-result-preparation.js +63 -0
  390. package/dist/services/merge-queue.js +228 -149
  391. package/dist/services/merge-server.js +161 -35
  392. package/dist/services/migrations.js +37 -16
  393. package/dist/services/model-catalog.js +109 -0
  394. package/dist/services/model-discovery.js +46 -0
  395. package/dist/services/native-daemon-history.js +49 -0
  396. package/dist/services/native-session-control.js +39 -0
  397. package/dist/services/operator-profiles.js +236 -0
  398. package/dist/services/plan-runtime.js +26 -11
  399. package/dist/services/plans.js +28 -25
  400. package/dist/services/projects.js +37 -17
  401. package/dist/services/prompts.js +28 -17
  402. package/dist/services/protocols.js +163 -87
  403. package/dist/services/provider-capacity.js +385 -0
  404. package/dist/services/recovery-observation-budget.js +121 -0
  405. package/dist/services/recovery-snapshot-database.js +560 -0
  406. package/dist/services/release-archive-producer.js +52 -0
  407. package/dist/services/release-archive.js +143 -0
  408. package/dist/services/release-cache.js +142 -0
  409. package/dist/services/release-compatibility.js +18 -0
  410. package/dist/services/release-download.js +67 -0
  411. package/dist/services/release-envelope.js +104 -0
  412. package/dist/services/release-manifest.js +45 -0
  413. package/dist/services/release-payload.js +114 -0
  414. package/dist/services/release-producer.js +63 -0
  415. package/dist/services/release-provenance.js +160 -0
  416. package/dist/services/release-registry.js +66 -0
  417. package/dist/services/release-source.js +194 -0
  418. package/dist/services/repair-intents.js +42 -0
  419. package/dist/services/review-copy.js +165 -0
  420. package/dist/services/review-deferral.js +23 -0
  421. package/dist/services/run-control-receipt.js +56 -0
  422. package/dist/services/run-control-worker.js +411 -0
  423. package/dist/services/run-control.js +1056 -0
  424. package/dist/services/run-controller-adapter.js +399 -0
  425. package/dist/services/run-controller-capture.js +186 -0
  426. package/dist/services/run-controller-process.js +208 -0
  427. package/dist/services/run-controller-recovery.js +289 -0
  428. package/dist/services/run-controller-state.js +97 -0
  429. package/dist/services/run-controller.js +1205 -0
  430. package/dist/services/run-engine-bindings.js +1 -1
  431. package/dist/services/run-fork.js +147 -0
  432. package/dist/services/run-health.js +149 -0
  433. package/dist/services/run-observations.js +152 -0
  434. package/dist/services/run-projection.js +22 -64
  435. package/dist/services/run-recovery-guard.js +77 -0
  436. package/dist/services/run-recovery-runtime.js +112 -0
  437. package/dist/services/run-recovery.js +489 -0
  438. package/dist/services/run-start-custody.js +177 -0
  439. package/dist/services/runs.js +1019 -247
  440. package/dist/services/runtime-budget.js +430 -0
  441. package/dist/services/runtime-command.js +43 -0
  442. package/dist/services/runtime-scope-capture.js +52 -0
  443. package/dist/services/runtime-scope-control.js +468 -0
  444. package/dist/services/runtime-scope-resume.js +599 -0
  445. package/dist/services/runtime-scope-stop.js +210 -0
  446. package/dist/services/runtime-scope-worker.js +287 -0
  447. package/dist/services/runtime-service.js +96 -0
  448. package/dist/services/schema-validation.js +10 -8
  449. package/dist/services/scope-writer-cleanup.js +27 -0
  450. package/dist/services/sessions.js +35 -69
  451. package/dist/services/setup.js +377 -0
  452. package/dist/services/stage-blocker.js +17 -7
  453. package/dist/services/stage-context.js +193 -19
  454. package/dist/services/stage-handlers.js +74 -0
  455. package/dist/services/stage-lifecycle.js +100 -108
  456. package/dist/services/stage-pause.js +89 -75
  457. package/dist/services/stage-report-renderer.js +234 -6
  458. package/dist/services/stage-work-graph.js +52 -0
  459. package/dist/services/tooling.js +32 -23
  460. package/dist/services/usage.js +49 -101
  461. package/dist/services/verification-matrix.js +374 -0
  462. package/dist/services/vnext-code-review.js +223 -88
  463. package/dist/services/vnext-code.js +327 -226
  464. package/dist/services/vnext-contracts.js +10 -1
  465. package/dist/services/vnext-execution-profile.js +80 -5
  466. package/dist/services/vnext-fanout.js +311 -19
  467. package/dist/services/vnext-merge.js +332 -111
  468. package/dist/services/vnext-plan-review.js +279 -127
  469. package/dist/services/vnext-plan.js +229 -104
  470. package/dist/services/vnext-protocolize.js +411 -86
  471. package/dist/services/vnext-specify.js +119 -81
  472. package/dist/services/vnext-workspace-policy.js +7 -2
  473. package/dist/services/work-registry.js +1627 -199
  474. package/dist/services/workspace-bootstrap.js +52 -15
  475. package/dist/services/workspace-files.js +85 -0
  476. package/dist/services/worktrees.js +98 -44
  477. package/dist/shared/entity-references.js +8 -0
  478. package/dist/shared/errors.js +17 -0
  479. package/dist/storage/agent-output.js +26 -0
  480. package/dist/storage/database.js +891 -39
  481. package/dist/storage/foreign-key-transaction.js +39 -0
  482. package/dist/storage/immutable-input.js +45 -0
  483. package/dist/storage/import-guard.js +102 -0
  484. package/dist/storage/native-cleanup.js +52 -0
  485. package/dist/storage/paths.js +17 -4
  486. package/dist/storage/projection-lock.js +73 -0
  487. package/dist/storage/work-references.js +23 -0
  488. package/dist/storage/writer-contract.js +89 -0
  489. package/dist/storage/writer-migration.js +85 -0
  490. package/package.json +43 -13
  491. package/tools/create-canon-release.mjs +33 -0
  492. package/tools/produce-hybrid-release.mjs +41 -0
  493. package/tools/repair-paused-run-status.mjs +84 -0
@@ -1,56 +1,105 @@
1
1
  import fs from "node:fs";
2
+ import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
3
+ import { requiredOption, optionalOption, optionalNumber, optionalPositiveNumber } from "./argument-values.js";
4
+ import { resolveCommandPaths, resolveCommandProjectRoot } from "../services/command-context.js";
5
+ import { assertAuxiliaryCommandScope, cliManagedInvocationScope, manualRecoveryInvocationScope, assertManualRecoveryInvocationTarget } from "../services/lifecycle-invocations.js";
2
6
  import path from "node:path";
7
+ import { randomUUID } from "node:crypto";
8
+ import { quote } from "shell-quote";
9
+ import { observeLifecycleCommand, observedLifecycleNativeEvent } from "../services/lifecycle-invocations.js";
10
+ import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
11
+ import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, bindLifecyclePublishedPacket, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleDiagnostic, observedLifecycleInvocation, resolveObservedLifecycleArgs, retainObservedLifecycleSemantics, settleLifecycleInvocation, settleLifecyclePreparationRejection, settleLifecycleRejection, validateLifecyclePathAliases } from "../services/lifecycle-invocations.js";
3
12
  import { createContext, createRouterContext } from "../runtime/context.js";
4
- import { helpForArgs } from "./help.js";
5
- import { AppError, isAppError } from "../shared/errors.js";
6
- import { writeJson } from "../shared/json.js";
7
- import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
8
- import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
9
- import { blockPlanItem, completePlanItem, getPlanStatus, skipPlanItem, startPlanItem } from "../services/plans.js";
10
- import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
13
+ import { dispatchNativeHook, isNativeHookIngress } from "./native-hook-ingress.js";
14
+ import { isRuntimeMaintenance, runtimeMaintenanceDiagnostics, withMaintenanceDiagnosticScope } from "./runtime-maintenance-diagnostics.js";
15
+ import { migrateStoreWriter } from "../storage/writer-migration.js";
16
+ import { resolveHelp } from "./help.js";
17
+ import { renderHumanError, writePreparationError } from "./error-output.js";
18
+ import { runOperatorProfilesCli } from "./operator-profiles.js";
19
+ import { runSetupCli } from "./setup.js";
20
+ import { runCompletionCli } from "./completion-cli.js";
21
+ import { runHarnessesCli } from "./harnesses.js";
22
+ import { runModelsCli } from "./models.js";
23
+ import { parseOutputOptions } from "./output-options.js";
24
+ import { AppError, errorRecord, isAppError } from "../shared/errors.js";
25
+ import { entityReferenceVariants } from "../shared/entity-references.js";
26
+ import { writeJson, parseJsonObject } from "../shared/json.js";
27
+ import { archiveProject, prepareProjectArchive, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
28
+ import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, prepareProtocolRegistration, requireProtocol, syncProtocolFromRun, prepareProtocolTransition, transitionProtocol } from "../services/protocols.js";
29
+ import { getPlanStatus, preparePlanItemMutation, updatePlanItem } from "../services/plans.js";
30
+ import { cancelMergeQueueJob, prepareMergeQueueCancellation, prepareMergeQueueWait, prepareMergeClaim, prepareMergeBundleClaim, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, prepareMergeBundleCompletion, completeMergeJob, prepareMergeJobCompletion, failMergeBundle, prepareMergeBundleFailure, failMergeJob, prepareMergeJobFailure, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, prepareMergeJobNote, waitNextMergeJob } from "../services/merge-queue.js";
11
31
  import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
12
- import { cleanupApply, cleanupScan } from "../services/cleanup.js";
13
- import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
14
- import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
15
- import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
16
- import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
17
- import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
32
+ import { cleanupApply, cleanupScan, prepareCleanupApply } from "../services/cleanup.js";
33
+ import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleDroidEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeHook, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, stageLifecycleMatchKey, initCodexHome, prepareCodexHome, prepareCodexHooks, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, prepareCodexHomeRemoval, removeCodexHooks } from "../services/hooks.js";
34
+ import { bootstrapWorktree, closeWorktree, createWorktreeRecord, prepareWorktreeCreate, prepareWorktreeBootstrap, prepareWorktreeClose, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
35
+ import { acquireLaneLock, cancelLaneWaiter, prepareLaneWaiterCancellation, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, prepareLaneWorkspace, prepareLaneLock, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
36
+ import { getProjectConfigStatus, prepareProjectConfigValue, setProjectConfigValue } from "../services/config.js";
37
+ import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegistration } from "../services/canon.js";
18
38
  import { getRuntimeStatus } from "../services/status.js";
19
39
  import { getCliVersionReport } from "../services/build-info.js";
20
40
  import { parseCommandArgs } from "../services/lifecycle-command.js";
21
- import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
41
+ import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile, prepareFileBytes, readBoundedUtf8Stream } from "./input-preparation.js";
42
+ import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
22
43
  import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
23
- import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
44
+ import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
24
45
  import { getProtocolBranchContext } from "../services/branch-context.js";
25
- import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, setFlowRunVariable, startFlowRun } from "../services/runs.js";
46
+ import { attachFlowRunStage, readFlowRunExecutionBundle, prepareFlowRunStageAttachment, prepareRawFlowRunStageCompletion, completeFlowRun, prepareFlowRunCompletion, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, prepareFlowRunConfig, prepareFlowRunVariable, setFlowRunVariable, startFlowRun, resolveRun, prepareFlowRunStart } from "../services/runs.js";
26
47
  import { previewNextEntityId } from "../services/ids.js";
27
48
  import { validateSchema } from "../services/schema-validation.js";
28
49
  import { preflightMemoryPermissions } from "../services/memory-permissions.js";
29
- import { ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
30
- import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, verifyMigrationReport } from "../services/migrations.js";
50
+ import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
51
+ import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
31
52
  import { publishProjectSummary } from "../services/project-summary.js";
32
53
  import { requireProjectByRoot } from "../services/projects.js";
33
- import { resolveProjectRoot } from "../storage/paths.js";
34
- import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
54
+ import { resolveProjectRoot, resolveDdFlowHome } from "../storage/paths.js";
55
+ import { doctorEngines, bindRunEngine, prepareRunEngineBind, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
35
56
  import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
36
- import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify } from "../services/vnext-specify.js";
37
- import { finishVnextProtocolize, isVnextProtocolizeRun, startVnextProtocolize } from "../services/vnext-protocolize.js";
38
- import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vnext-plan.js";
39
- import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
40
- import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
41
- import { addVnextCodeReviewRepair, finishVnextCodeReview, isVnextCodeReviewRun, startVnextCodeReview } from "../services/vnext-code-review.js";
42
- import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
57
+ import { getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, prepareVnextSpecifyStart } from "../services/vnext-specify.js";
58
+ import { isVnextProtocolizeRun, prepareProtocolizeResultPath, prepareVnextProtocolizeStart } from "../services/vnext-protocolize.js";
59
+ import { isVnextPlanRun, prepareVnextPlanStart, prepareVnextPlanFinish } from "../services/vnext-plan.js";
60
+ import { dispatchVnextPlanReview, isVnextPlanReviewRun, prepareVnextPlanReviewStart, recordVnextPlanReviewCapacity } from "../services/vnext-plan-review.js";
61
+ import { addVnextCodeRepair, prepareVnextCodeRepair, prepareVnextCodeStart } from "../services/vnext-code.js";
62
+ import { isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart } from "../services/vnext-code-review.js";
63
+ import { applyVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest } from "../services/vnext-merge.js";
64
+ import { sdlcStageHandlers } from "../services/stage-handlers.js";
65
+ import { startMemoryStage, finishMemoryStage } from "../services/memory-stage-dispatch.js";
66
+ import { bindStageCoordinatorWork } from "../services/work-registry.js";
67
+ import { registerMemoryCompositionFromStage } from "../services/memory-compose-handler.js";
68
+ import { runMemoryVerificationChecks } from "../services/memory-verify-handler.js";
69
+ import { registerMemoryReviewFromStage, collectMemoryInitialReviewFromStage } from "../services/memory-review-handler.js";
70
+ import { PreparedRetainedMemoryReviewAuthority } from "../services/memory-retained-review-authority.js";
71
+ import { PreparedRetainedMemoryReviewResults } from "../services/memory-retained-review-results.js";
72
+ import { registerMemoryCorrectionsFromStage, registerMemorySecondCorrectionsFromStage, runMemoryCorrectionChecksFromStage, runMemorySecondCorrectionChecksFromStage, registerCorrectedMemoryReviewFromStage, registerSecondCorrectedMemoryReviewFromStage, collectCorrectedMemoryReviewFromStage, collectSecondCorrectedMemoryReviewFromStage } from "../services/memory-review-handler.js";
73
+ import { integrateMemoryFinishFromStage, registerMemoryFinishEvidenceFromStage } from "../services/memory-finish-handler.js";
74
+ import { registerMemoryRepairBaselineFromStage, registerMemorySecondRepairBaselineFromStage, reserveMemoryRepairWriterCopyFromStage, reserveMemorySecondRepairCopyFromStage, materializeMemoryRepairWriterCopyFromStage, materializeMemorySecondRepairCopyFromStage, prepareMemoryRepairDispatchFromStage, prepareMemorySecondRepairDispatchFromStage, registerMemoryRepairCurrentViewFromStage, registerMemorySecondRepairCurrentViewFromStage, registerMemoryRepairPublicationSourceFromStage, reserveMemoryRepairPublicationAgainstSourceFromStage, reserveMemorySecondRepairPublicationFromStage, applyMemoryRepairPublicationFromStage, applyMemorySecondRepairPublicationFromStage, finishMemoryRepairPublicationFromStage, finishMemorySecondRepairPublicationFromStage } from "../services/memory-review-handler.js";
43
75
  import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
44
- import { getVnextFanoutStatus } from "../services/vnext-fanout.js";
45
- import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
46
- import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
76
+ import { getVnextFanoutStatus, memoryStageFanoutDescriptor, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
77
+ import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume, stagePauseCommand } from "../services/stage-pause.js";
78
+ import { runtimeCommand } from "../services/runtime-command.js";
79
+ import { managedLifecycleCommand } from "../services/lifecycle-invocations.js";
80
+ import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRuntimeBlock, prepareStageRuntimeUnblock } from "../services/stage-blocker.js";
47
81
  import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
48
- import { renderWorkerPrompt } from "../services/prompts.js";
49
- import { finishStage, startStage } from "../services/stage-lifecycle.js";
50
- import { addWorkBatch, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
51
- import { createEvalBootstrapSnapshot, createEvalRunSnapshot, prepareVnextSpecifyRun, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
52
- import { loadExternalStageContext } from "../services/stage-context.js";
53
- import { confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessStatus, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
82
+ import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
83
+ import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
84
+ import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, workResultByteLimit, retryWork, showWork, startWork } from "../services/work-registry.js";
85
+ import { launchExternalWork } from "../services/external-work-launch.js";
86
+ import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
87
+ import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
88
+ import { acceptRunRecovery, beginRunRecovery, inspectRunRecovery, prepareRecoveryCapture, prepareRecoveryPayload, prepareRunRecoveryResume, recordRecoveryCapture, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
89
+ import { loadExternalStageContext, prepareExternalStageContext } from "../services/stage-context.js";
90
+ import { vnextStageDirectory } from "../domain/stage-catalog.js";
91
+ import { prepareCommandRoute } from "./command-inputs.js";
92
+ import { parseLifecycleCommand } from "../services/lifecycle-command.js";
93
+ import { dispatchRuntimeProcess, prepareRuntimeProcess } from "./runtime-process.js";
94
+ import { reconcileFlowRunHealth } from "../services/run-health.js";
95
+ import { fenceRuntimeBudget, runtimeBudgetStatus, prepareRuntimeScopeControl, prepareRuntimeScopeFence } from "../services/runtime-budget.js";
96
+ import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
97
+ import { launchRuntimeScopeControl, launchRuntimeScopeResume, prepareRuntimeScopeReconciliation, prepareRuntimeScopeServing, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
98
+ import { prepareRuntimeScopeResumeRequest } from "../services/runtime-scope-resume.js";
99
+ import { launchRunController, prepareRunControllerLaunch, resumeRunController, serveRunController, runControllerStatus, answerRunController, prepareRunControllerAnswer, supplyRunControllerContext, prepareRunControllerContext } from "../services/run-controller.js";
100
+ import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
101
+ import { reconcileRunControl, requestRunControl, serveRunControl } from "../services/run-control-worker.js";
102
+ import { startRuntimeService, prepareRuntimeService } from "../services/runtime-service.js";
54
103
  const defaultIo = {
55
104
  stdout: process.stdout,
56
105
  stderr: process.stderr,
@@ -58,9 +107,58 @@ const defaultIo = {
58
107
  };
59
108
  const flagValue = "__dd_flow_flag__";
60
109
  export async function runCli(args, io = defaultIo, env = process.env) {
61
- const output = parseOutputOptions(args);
110
+ return withMaintenanceDiagnosticScope(() => runCliRequest(args, io, env));
111
+ }
112
+ async function runCliRequest(args, io, env) {
113
+ try {
114
+ const help = resolveHelp(args);
115
+ if (help) {
116
+ if (help.json)
117
+ writeJson(io.stdout, help.data);
118
+ else
119
+ io.stdout.write(`${help.text}\n`);
120
+ return 0;
121
+ }
122
+ }
123
+ catch (error) {
124
+ const record = errorRecord(error);
125
+ writePreparationError(io, args, record);
126
+ return record.exitCode;
127
+ }
128
+ let output;
129
+ try {
130
+ output = parseOutputOptions(args);
131
+ }
132
+ catch (error) {
133
+ const rejection = isAppError(error) ? error : new AppError("usage", error instanceof Error ? error.message : String(error), 2);
134
+ Object.assign(rejection.details, { phase: "prepare", effect: "no_effect", recoverable: true, ...rejection.details });
135
+ try {
136
+ const command = quote(["dd-flow", ...args]);
137
+ const analysis = parseLifecycleCommand(command);
138
+ const event = analysis.kind !== "none" || analysis.auxiliary
139
+ ? observedLifecycleNativeEvent(createRouterContext(env), command, env.DD_FLOW_DAEMON_ID) : null;
140
+ if (event) {
141
+ const context = createContext({ ...env }, "hook");
142
+ try {
143
+ observeLifecycleCommand(context, command, event)({ error: rejection });
144
+ }
145
+ finally {
146
+ context.db.close?.();
147
+ }
148
+ }
149
+ }
150
+ catch (persistence) {
151
+ rejection.details.lifecycle_diagnostic_error = String(persistence);
152
+ }
153
+ writePreparationError(io, args, errorRecord(rejection));
154
+ return rejection.exitCode;
155
+ }
62
156
  const progress = progressForCommand(output.args);
63
157
  let heartbeat;
158
+ let invocationSettlement;
159
+ let settleHook;
160
+ let diagnosticHome;
161
+ let maintenanceDiagnostics;
64
162
  try {
65
163
  if (output.args.length === 1 && output.args[0] === "--version") {
66
164
  const payload = getCliVersionReport();
@@ -70,11 +168,60 @@ export async function runCli(args, io = defaultIo, env = process.env) {
70
168
  io.stdout.write(`dd-flow ${payload.cli.version}\n`);
71
169
  return 0;
72
170
  }
73
- const help = helpForArgs(output.args);
74
- if (help) {
75
- io.stdout.write(`${help}\n`);
171
+ if (output.args[0] === "completion")
172
+ return runCompletionCli(args, io);
173
+ if (output.args[0] === "setup")
174
+ return runSetupCli(args, env, io);
175
+ if (output.args[0] === "harnesses")
176
+ return runHarnessesCli(args, env, io);
177
+ if (output.args[0] === "models")
178
+ return runModelsCli(args, io);
179
+ if (output.args[0] === "profiles")
180
+ return runOperatorProfilesCli(args, env, io);
181
+ const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
182
+ if (isNativeHookIngress(output.args)) {
183
+ writeJson(io.stdout, await dispatchNativeHook(output.args, await readStdin(io.stdin), env));
76
184
  return 0;
77
185
  }
186
+ if (output.args.slice(0, 3).join(" ") === "codex hook handle") {
187
+ const parsed = prepareCommandRoute(output.args).parsed;
188
+ const stdin = await readStdin(io.stdin);
189
+ const payload = parseJsonObject(stdin || "{}", "codex hook stdin");
190
+ const event = requiredOption(parsed, "event");
191
+ const isolatedHome = event === "PreToolUse" ? isolatedDdFlowHomeFromHookStdin(stdin) : null;
192
+ const hookEnv = isolatedHome ? { ...env, DD_FLOW_HOME: isolatedHome } : env;
193
+ const proof = await verifyCodexHookDelivery(hookEnv, payload);
194
+ let opened;
195
+ const mode = databaseModeForCommand(output.args, classifyCliOperation(output.args, hookEnv));
196
+ const context = {
197
+ env: hookEnv, ddFlowHome: resolveDdFlowHome(hookEnv), now: () => new Date().toISOString(),
198
+ get db() { return (opened ??= createContext(hookEnv, mode)).db; }
199
+ };
200
+ try {
201
+ // Retired events retain the explicit startup/migration path. PreToolUse
202
+ // only opens its selected prepared store when the handler needs it.
203
+ if (mode !== "hook")
204
+ opened = createContext(hookEnv, mode);
205
+ if (proof)
206
+ context.codexHookProof = proof;
207
+ const result = handleCodexHook(context, { event, projectRoot: optionalOption(parsed, "project-root"), stdin });
208
+ writeJson(io.stdout, result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result
209
+ ? { hookSpecificOutput: result.hookSpecificOutput } : {});
210
+ return 0;
211
+ }
212
+ finally {
213
+ opened?.db.close?.();
214
+ }
215
+ }
216
+ if (output.args[0] === "run" && ["drive", "control"].includes(output.args[1] ?? "")) {
217
+ const controlArgs = parseArgs(output.args.slice(2));
218
+ requiredOption(controlArgs, "run");
219
+ requiredOption(controlArgs, "project-root");
220
+ }
221
+ if (io.stdin?.isTTY && ["work", "stage"].includes(output.args[0] ?? "") && output.args[1] === "finish"
222
+ && hasOption(parseArgs(output.args.slice(2)), "result-stdin")) {
223
+ throw new AppError("usage", "--result-stdin requires non-interactive input. Pipe or redirect the complete JSON into this command, or use --result-file; interactive terminals can truncate long JSON and wait indefinitely for EOF.", 2);
224
+ }
78
225
  const classification = classifyCliOperation(output.args, env);
79
226
  if (requiresExplicitUpgradeAuthorization(output.args) && classification.mode !== "mb_upgrade") {
80
227
  throw new AppError("upgrade_authorization_required", "This mb-upgrade command requires the explicit mb-upgrade authorization marker and allowlisted target context", 1, {
@@ -82,26 +229,191 @@ export async function runCli(args, io = defaultIo, env = process.env) {
82
229
  required_marker: "DD_FLOW_COMPATIBILITY_MODE=mb-upgrade"
83
230
  });
84
231
  }
85
- const routerContext = createRouterContext(env);
232
+ // Registration may bootstrap the resource registry, but never the unrelated
233
+ // RUN store. Its caller's absolute deadline also bounds that first open.
234
+ const maintenanceOptions = isRuntimeMaintenance(output.args) ? { resourceDatabaseMode: output.args[2] === "register" ? "initialize" : output.args[2] === "status" ? "read_existing" : "prepared_existing", ...(env.DD_FLOW_HOOK_DEADLINE_AT ? { databaseDeadlineAt: Number(env.DD_FLOW_HOOK_DEADLINE_AT) } : {}) } : {};
235
+ if (output.args[0] === "runtime" && output.args[1] === "scope" && ["fence", "stop"].includes(output.args[2] ?? ""))
236
+ maintenanceOptions.resourceDatabaseMode = "retirement";
237
+ // Keep ordinary CLI JSON/stderr compatibility; bounded maintenance callers
238
+ // provide this request's deadline and consume its structured phase evidence.
239
+ if (maintenanceOptions.databaseDeadlineAt !== undefined)
240
+ maintenanceDiagnostics = runtimeMaintenanceDiagnostics(output.args, maintenanceOptions.databaseDeadlineAt, line => io.stderr.write(line));
241
+ const routerContext = createRouterContext(env, maintenanceOptions.databaseDeadlineAt);
242
+ const publicCommand = quote(["dd-flow", ...output.args]);
243
+ const nativeCommand = quote(["dd-flow", ...args]);
244
+ const lifecycleCommand = parseLifecycleCommand(publicCommand);
245
+ if (lifecycleCommand.kind === "standalone")
246
+ validateLifecyclePathAliases(output.args, lifecycleCommand.invocation.operation);
247
+ const managedScope = lifecycleCommand.kind === "standalone" && env.DD_FLOW_DAEMON_ID ? cliManagedInvocationScope({ ...routerContext, env }) : null;
248
+ const nativeEvent = env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone"
249
+ ? observedLifecycleNativeEvent(routerContext, nativeCommand, env.DD_FLOW_DAEMON_ID) : null;
250
+ const nativeEventKey = nativeEvent && routerContext.db.get("SELECT event_key FROM hook_events WHERE project_id = ? AND id = ?", [nativeEvent.project_id, nativeEvent.id])?.event_key;
251
+ const observedInvocation = managedScope ? observedLifecycleInvocation(routerContext, publicCommand, managedScope, nativeEventKey ? { eventKey: nativeEventKey } : undefined) : null;
252
+ if (managedScope && suppliedInvocationId
253
+ && routerContext.db.get("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'lifecycle_invocations'")
254
+ && routerContext.db.get("SELECT id FROM lifecycle_invocations WHERE id = ?", [suppliedInvocationId])) {
255
+ const suppliedScope = lifecycleInvocationScope({ ...routerContext, env }, suppliedInvocationId);
256
+ if (["projectRoot", "daemonId", "rootSessionId", "runId", "generation"].some(key => suppliedScope[key] !== managedScope[key])) {
257
+ throw new AppError("invocation_scope_mismatch", "Explicit lifecycle assignment differs from the current physical native owner", 1, { effect: "no_effect", recoverable: false });
258
+ }
259
+ }
260
+ const diagnostic = env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone"
261
+ ? observedLifecycleDiagnostic(routerContext, publicCommand, env.DD_FLOW_DAEMON_ID, observedInvocation?.id) : null;
262
+ if (diagnostic && (!nativeEvent || Number(diagnostic.hook_event_id) > nativeEvent.id)) {
263
+ throw new AppError("lifecycle_shell_syntax_invalid", `Native observer rejected the shell form: ${String(diagnostic.reason ?? "unsupported shell composition")}. Write structured input to a file and run the returned dd-flow command separately.`, 2, { ...diagnostic, phase: "prepare", effect: "no_effect", recoverable: true });
264
+ }
265
+ let manualRecoveryHookEventKey;
266
+ if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
267
+ const receipt = nativeEvent && routerContext.db.get("SELECT event_key,harness,provider_session_id,parent_session_id,daemon_id,status FROM hook_events WHERE id = ? AND project_id = ?", [nativeEvent.id, nativeEvent.project_id]);
268
+ const manual = managedScope && receipt?.status === "observed" ? manualRecoveryInvocationScope({ ...routerContext, env }, managedScope.projectRoot, { daemonId: receipt.daemon_id, rootSessionId: receipt.provider_session_id, sessionId: receipt.provider_session_id, parentSessionId: receipt.parent_session_id, toolCallId: String(nativeEvent.id) }, receipt.harness) : null;
269
+ if (!manual || JSON.stringify(manual) !== JSON.stringify(managedScope))
270
+ throw new AppError(nativeEvent ? "invocation_assignment_missing" : "invocation_receipt_missing", nativeEvent ? "Native call was observed, but its arguments do not match an issued lifecycle assignment" : "Managed lifecycle command has no committed native receipt", 1, { effect: "no_effect", recoverable: false });
271
+ assertManualRecoveryInvocationTarget({ ...routerContext, env }, manual, publicCommand);
272
+ manualRecoveryHookEventKey = receipt.event_key;
273
+ }
274
+ const invocationId = observedInvocation?.id ?? suppliedInvocationId;
275
+ let boundInvocationArgs;
276
+ try {
277
+ boundInvocationArgs = observedInvocation
278
+ ? resolveObservedLifecycleArgs(routerContext, observedInvocation.id, output.args)
279
+ : invocationId && invocationId !== suppliedInvocationId
280
+ ? [...output.args.filter((_, index, values) => values[index - 1] !== "--invocation-id" && values[index] !== "--invocation-id"), "--invocation-id", invocationId]
281
+ : output.args;
282
+ }
283
+ catch (error) {
284
+ if (!(observedInvocation && error instanceof AppError))
285
+ throw error;
286
+ const rejectionContext = createContext({ ...env }, "hook");
287
+ try {
288
+ Object.assign(error.details, settleLifecyclePreparationRejection(rejectionContext, observedInvocation.id, error));
289
+ }
290
+ finally {
291
+ rejectionContext.db.close?.();
292
+ }
293
+ throw error;
294
+ }
295
+ const observedOperation = lifecycleCommand.kind === "standalone" ? lifecycleCommand.invocation.operation : null;
296
+ if (observedInvocation && !observedOperation)
297
+ throw new AppError("invocation_command_invalid", "Observed lifecycle command could not be parsed", 1);
298
+ const invocationScope = invocationId ? lifecycleInvocationScope({ ...routerContext, env }, invocationId) : undefined;
299
+ const expandedInvocationArgs = invocationScope && observedOperation
300
+ ? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
301
+ : boundInvocationArgs;
302
+ const retainedOutput = observedInvocation ? parseOutputOptions(expandedInvocationArgs) : null;
303
+ if (retainedOutput?.responseFile)
304
+ output.responseFile = retainedOutput.responseFile;
305
+ let invocationArgs = retainedOutput?.args ?? expandedInvocationArgs;
86
306
  emitHumanProgress(io, output, progress, "start");
87
307
  if (output.progressJsonl) {
88
308
  writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
89
309
  heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
90
310
  }
91
- const scopeProjectRoot = resolveProjectRootBeforeRouting(output.args, env);
92
- const routedArgs = output.responseFile ? [...output.args, "--response-file", output.responseFile] : output.args;
311
+ // Route by retained authority, so a mistyped project path reaches the CLI
312
+ // validator instead of selecting a different engine or failing preflight.
313
+ const scopeProjectRoot = invocationScope?.projectRoot ?? resolveCommandProjectRoot(routerContext, output.args, resolveProjectRootBeforeRouting(output.args, env, maintenanceOptions.databaseDeadlineAt));
314
+ assertAuxiliaryCommandScope({ ...routerContext, env }, publicCommand, scopeProjectRoot);
315
+ invocationArgs = resolveCommandPaths(routerContext, invocationArgs, scopeProjectRoot);
316
+ const routedArgs = output.responseFile ? [...invocationArgs, "--response-file", output.responseFile] : invocationArgs;
93
317
  const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
94
318
  if (routed?.routed && !routed.inProcess) {
95
319
  if (heartbeat)
96
320
  clearInterval(heartbeat);
97
- emitHumanProgress(io, output, progress, "done");
321
+ emitHumanProgress(io, output, progress, routed.exitCode === 0 ? "done" : "failed");
322
+ if (output.progressJsonl && routed.exitCode !== 0)
323
+ writeProgressJsonl(io, "failed", "Routed engine failed", { exit_code: routed.exitCode });
98
324
  return routed.exitCode;
99
325
  }
326
+ let preparedInput;
327
+ let preparedRoute;
328
+ if (!invocationId) {
329
+ const preparationContext = createContext({ ...env }, "read_existing", undefined, maintenanceOptions);
330
+ try {
331
+ preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
332
+ }
333
+ finally {
334
+ preparationContext.db.close?.();
335
+ }
336
+ }
100
337
  const dispatchContext = routed?.inProcess
101
- ? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(output.args, classification))
102
- : createContext(env, databaseModeForCommand(output.args, classification));
103
- preflightCliCompatibility(dispatchContext, output.args, env, scopeProjectRoot);
104
- const result = await dispatch(output.args, dispatchContext, io, scopeProjectRoot, classification, output);
338
+ ? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(invocationArgs, classification), undefined, maintenanceOptions)
339
+ : createContext({ ...env }, databaseModeForCommand(invocationArgs, classification), undefined, maintenanceOptions);
340
+ diagnosticHome = dispatchContext.ddFlowHome;
341
+ dispatchContext.env.DD_FLOW_INVOCATION_READONLY = classification.mode === "read_only_diagnostics" ? "1" : undefined;
342
+ let dispatchArgs = invocationArgs;
343
+ let retainedOutcome;
344
+ // Issued transports may deliver their receipt asynchronously. Observe only
345
+ // after admission; retained outcomes must not need a fresh hook receipt.
346
+ if (invocationId) {
347
+ const command = quote(["dd-flow", ...invocationArgs]);
348
+ const admission = await awaitLifecycleInvocation(dispatchContext, { id: invocationId, command,
349
+ validateArgs: () => {
350
+ try {
351
+ preparedRoute = prepareCommandRoute(invocationArgs);
352
+ }
353
+ catch (error) {
354
+ if (error instanceof AppError && error.code === "usage" && ["invocation-id", "hook-event-id"].includes(String(error.details.parameter ?? ""))) {
355
+ throw new AppError("lifecycle_contract_invalid", `The runtime generated arguments rejected by its own CLI contract: ${error.message}`, 1, { ...error.details, recoverable: false, operation: observedOperation });
356
+ }
357
+ throw error;
358
+ }
359
+ },
360
+ prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
361
+ assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
362
+ if (admission.replay) {
363
+ retainedOutcome = admission.outcome;
364
+ settleHook = undefined;
365
+ delete dispatchContext.observeLifecycleHook;
366
+ }
367
+ else {
368
+ invocationSettlement = { context: dispatchContext, id: invocationId };
369
+ if (observedInvocation)
370
+ retainObservedLifecycleSemantics(dispatchContext, invocationId, invocationArgs);
371
+ dispatchArgs = [...invocationArgs, "--hook-event-id", admission.eventKey];
372
+ preparedInput?.route?.parsed.options.set("hook-event-id", [admission.eventKey]);
373
+ dispatchContext.env.DD_FLOW_INVOCATION_SCOPE = JSON.stringify(lifecycleInvocationScope(dispatchContext, invocationId));
374
+ dispatchContext.env.DD_FLOW_CURRENT_INVOCATION = invocationId;
375
+ }
376
+ }
377
+ if (!invocationId && manualRecoveryHookEventKey) {
378
+ // Keep the physically scoped receipt selected by admission; a service must
379
+ // not rediscover a different daemon's matching command event.
380
+ if (!invocationArgs.includes("--hook-event-id"))
381
+ dispatchArgs = [...invocationArgs, "--hook-event-id", manualRecoveryHookEventKey];
382
+ preparedInput?.route?.parsed.options.set("hook-event-id", [manualRecoveryHookEventKey]);
383
+ }
384
+ if (retainedOutcome?.error) {
385
+ const error = retainedOutcome.error;
386
+ throw new AppError(error.code, error.message, error.exitCode, error.details);
387
+ }
388
+ if (!retainedOutcome && !preparedInput)
389
+ preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
390
+ if (!retainedOutcome)
391
+ await preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
392
+ if (!retainedOutcome) {
393
+ const command = !invocationId ? nativeCommand : quote(["dd-flow", ...dispatchArgs]);
394
+ const event = !invocationId ? nativeEvent : null;
395
+ settleHook = observeLifecycleCommand(dispatchContext, command, event ?? undefined);
396
+ }
397
+ const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
398
+ if (!retainedOutcome && invocationArgs[0] === "stage" && invocationArgs[1] === "start"
399
+ && result && typeof result === "object" && "prompt_path" in result && typeof result.prompt_path === "string") {
400
+ try {
401
+ bindLifecyclePublishedPacket(dispatchContext, fs.readFileSync(result.prompt_path, "utf8"));
402
+ }
403
+ catch (cause) {
404
+ throw new AppError("invocation_packet_binding_failed", "Stage started but its published lifecycle assignments could not be retained", 1, { effect: "committed", cause: String(cause) });
405
+ }
406
+ }
407
+ const completedHook = settleHook;
408
+ settleHook = undefined;
409
+ completedHook?.({ result });
410
+ if (invocationSettlement) {
411
+ const completed = invocationSettlement;
412
+ invocationSettlement = undefined;
413
+ // A failure to persist an already completed result is an unknown reply,
414
+ // not a failed operation eligible for a new productive retry.
415
+ settleLifecycleInvocation(completed.context, completed.id, { result });
416
+ }
105
417
  const refreshWarnings = refreshDashboardsAfterMutation(dispatchContext, output.args, result, scopeProjectRoot);
106
418
  const resultWithWarnings = addWarnings(result, refreshWarnings);
107
419
  if (heartbeat)
@@ -111,47 +423,157 @@ export async function runCli(args, io = defaultIo, env = process.env) {
111
423
  emitHumanProgress(io, output, progress, "done");
112
424
  const payload = attachProgress(resultWithWarnings, progress, output);
113
425
  const visible = writeResponseFile(payload, output);
426
+ const maintenanceReceipt = payload;
427
+ const maintenanceExpiry = maintenanceReceipt?.lease_expires_at ?? maintenanceReceipt?.process?.lease_expires_at;
428
+ maintenanceDiagnostics?.phase("ack", { ...(typeof maintenanceExpiry === "string" && Number.isFinite(Date.parse(maintenanceExpiry)) ? { lease_expires_at: maintenanceExpiry } : {}) });
114
429
  if (output.json)
115
430
  writeJson(io.stdout, visible);
116
431
  else
117
432
  io.stdout.write(renderHumanResult(output.args, payload));
118
433
  return completedCommandExitCode(payload);
119
434
  }
120
- catch (error) {
435
+ catch (caught) {
436
+ const record = errorRecord(caught);
437
+ maintenanceDiagnostics?.failure(caught);
438
+ const error = isAppError(caught) ? caught : Object.assign(new AppError(record.code, record.message, record.exitCode, { effect: "unknown", ...record.details, ...(caught?.errcode !== undefined ? { sqlite_extended_code: caught.errcode } : {}) }), { ...(record.cause ? { cause: record.cause } : {}), ...(record.cleanup_error ? { cleanup_error: record.cleanup_error } : {}), ...(record.retryable !== undefined ? { retryable: record.retryable } : {}) });
439
+ if (!settleHook && !invocationSettlement) {
440
+ let earlyContext;
441
+ try {
442
+ const command = quote(["dd-flow", ...args]);
443
+ const analysis = parseLifecycleCommand(command);
444
+ // Maintenance failures must not start an unrelated, unbounded hook
445
+ // lookup after their own request deadline has already expired.
446
+ const router = analysis.kind !== "none" || analysis.auxiliary ? createRouterContext(env) : null;
447
+ const event = router ? observedLifecycleNativeEvent(router, command, env.DD_FLOW_DAEMON_ID) : null;
448
+ // A correctable prepare rejection precedes the terminal command's
449
+ // single-use claim. Correlate its unique hook by the same lifecycle
450
+ // match key used at dispatch, including compound provider tool shells;
451
+ // otherwise a repaired retry sees two fresh, unclaimed events.
452
+ if (router && (event || (error.details.phase === "prepare" && error.details.effect === "no_effect"
453
+ && fs.existsSync(path.join(router.ddFlowHome, "db.sqlite"))))) {
454
+ earlyContext = createContext({ ...env }, "hook");
455
+ observeLifecycleCommand(earlyContext, command, event ?? undefined)({ error });
456
+ }
457
+ }
458
+ catch (persistence) {
459
+ error.details.lifecycle_diagnostic_error = String(persistence);
460
+ }
461
+ finally {
462
+ earlyContext?.db.close?.();
463
+ }
464
+ }
465
+ if (diagnosticHome && (!isAppError(caught) || error.code === "storage_write_failed")) {
466
+ try {
467
+ const directory = path.join(diagnosticHome, "diagnostics", "cli");
468
+ fs.mkdirSync(directory, { recursive: true });
469
+ const artifact = path.join(directory, `${randomUUID()}.json`);
470
+ fs.writeFileSync(artifact, JSON.stringify({ code: error.code, message: error.message, details: error.details, stack: caught instanceof Error ? caught.stack : null }), { flag: "wx", mode: 0o600 });
471
+ error.details.diagnostic_artifact = artifact;
472
+ }
473
+ catch (secondary) {
474
+ io.stderr.write(`CLI diagnostic persistence failed: ${String(secondary)}\n`);
475
+ }
476
+ }
121
477
  if (heartbeat)
122
478
  clearInterval(heartbeat);
123
- const payload = isAppError(error)
124
- ? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
125
- : { ok: false, error: { code: "unexpected", message: String(error) } };
479
+ if (invocationSettlement && error.details.effect !== "committed") {
480
+ try {
481
+ if (isAppError(error)) {
482
+ delete error.details.retry_command;
483
+ delete error.details.retry_instruction;
484
+ const settled = errorRecord(error);
485
+ settleLifecycleRejection(invocationSettlement.context, invocationSettlement.id, settled);
486
+ // Serialization owns a copy. Publish the committed settlement, not
487
+ // the original rejection which has no successor authority.
488
+ Object.assign(error.details, settled.details);
489
+ }
490
+ else
491
+ settleLifecycleInvocation(invocationSettlement.context, invocationSettlement.id, { error: { code: "unexpected", message: String(error), exitCode: 1, details: {} } });
492
+ }
493
+ catch (persistenceError) {
494
+ if (isAppError(error))
495
+ error.details.recoverable = false;
496
+ io.stderr.write(`Invocation settlement persistence failed: ${String(persistenceError)}\n`);
497
+ }
498
+ }
499
+ if (settleHook) {
500
+ try {
501
+ settleHook({ error: isAppError(error) ? error : { code: "unexpected", message: String(error), details: {} } });
502
+ }
503
+ catch (persistence) {
504
+ io.stderr.write(`${JSON.stringify({ primary: String(error), lifecycle_persistence_error: String(persistence) })}\n`);
505
+ }
506
+ }
507
+ const payload = { ok: false, error: errorRecord(error) };
508
+ progress.splice(0, progress.length, ...progress.filter(event => event.phase !== "done"));
509
+ if (progress.length)
510
+ progress.push({ phase: "failed", message: payload.error.message, at: new Date().toISOString() });
126
511
  emitHumanProgress(io, output, progress, "failed");
127
512
  if (output.progressJsonl)
128
513
  writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
129
514
  const errorPayload = attachProgress(payload, progress, output);
130
- const visible = writeResponseFile(errorPayload, output);
515
+ let visible = errorPayload;
516
+ try {
517
+ if (error.details.parameter !== "response-file")
518
+ visible = writeResponseFile(errorPayload, output);
519
+ }
520
+ catch (publication) {
521
+ error.details.response_publication_error = { code: publication?.code ?? "response_publication_failed", message: publication instanceof Error ? publication.message : String(publication) };
522
+ }
131
523
  if (output.json && output.progressJsonl)
132
524
  writeJson(io.stdout, errorPayload);
133
525
  else if (output.json)
134
526
  writeJson(io.stderr, visible);
135
527
  else
136
- io.stderr.write(renderHumanError(payload));
528
+ io.stderr.write(renderHumanError(payload, args));
137
529
  return isAppError(error) ? error.exitCode : 1;
138
530
  }
531
+ finally {
532
+ maintenanceDiagnostics?.close();
533
+ }
139
534
  }
140
- function resolveProjectRootBeforeRouting(args, env) {
535
+ function resolveProjectRootBeforeRouting(args, env, deadlineAt) {
141
536
  const explicit = resolveExplicitProjectRoot(args);
142
537
  if (explicit)
143
538
  return explicit;
144
539
  try {
145
540
  // Router identity lookup is read-only; it must not initialize or migrate runtime storage.
146
- return resolveOperationProjectRoot(createContext(env, "read_existing"), args);
541
+ const context = createContext(env, "read_existing", undefined, deadlineAt === undefined ? {} : { databaseDeadlineAt: deadlineAt });
542
+ try {
543
+ return resolveOperationProjectRoot(context, args);
544
+ }
545
+ finally {
546
+ context.db.close?.();
547
+ }
147
548
  }
148
549
  catch {
149
550
  return null;
150
551
  }
151
552
  }
152
553
  function databaseModeForCommand(args, classification) {
554
+ if (isNativeHookIngress(args))
555
+ return "hook";
556
+ if (args[0] === "run" && args[1] === "reconcile")
557
+ return args.includes("--apply") ? "reconcile" : "read_existing";
558
+ if (isRuntimeMaintenance(args))
559
+ return "read_existing";
153
560
  const [family, command] = args;
561
+ // Managed attempts are issued only from a prepared store. Opening that
562
+ // existing writer must not run migrations before their input is admitted.
563
+ if (parseArgs(args.slice(2)).options.has("invocation-id"))
564
+ return "hook";
565
+ // Native hook ingress must remain synchronous, but it must not run schema
566
+ // migration/VACUUM while the provider is waiting for its decision. Startup
567
+ // prepares the store; the hook mode only verifies the prepared contract and
568
+ // records the receipt.
569
+ if (family === "codex" && command === "hook" && args[2] === "handle") {
570
+ // Only the participating PreToolUse ingress needs the prepared hot-path
571
+ // contract. SessionStart/Stop/PostToolUse are observational no-ops here
572
+ // and retain the ordinary startup path for backwards compatibility.
573
+ return optionalOption(parseArgs(args.slice(3)), "event") === "PreToolUse" ? "hook" : "initialize";
574
+ }
154
575
  if (family === "engine" ||
576
+ family === "storage" && command === "migrate-writer" ||
155
577
  family === "version" ||
156
578
  family === "schema" ||
157
579
  family === "canon" && command === "resolve" ||
@@ -165,86 +587,6 @@ function databaseModeForCommand(args, classification) {
165
587
  return "read_existing";
166
588
  return "initialize";
167
589
  }
168
- function renderHumanError(payload) {
169
- const lines = [`dd-flow: ${payload.error.message}`];
170
- const details = recordObject(payload.error.details);
171
- const compatibility = recordObject(details?.compatibility);
172
- const remediation = recordObject(details?.remediation);
173
- if (compatibility) {
174
- lines.push(`Compatibility: ${String(compatibility.verdict ?? "unknown")} (${String(compatibility.engine_resolution ?? "unknown")})`);
175
- if (compatibility.blocked_operation)
176
- lines.push(`Blocked operation: ${String(compatibility.blocked_operation)}`);
177
- if (compatibility.install_hint)
178
- lines.push(`Install compatible engine: ${String(compatibility.install_hint)}`);
179
- }
180
- if (remediation?.mb_upgrade_mode)
181
- lines.push(`Upgrade mode: ${String(remediation.mb_upgrade_mode)}`);
182
- if (details?.preferred_command)
183
- lines.push(`Preferred command: ${String(details.preferred_command)}`);
184
- const related = Array.isArray(details?.related_commands) ? details.related_commands.map(String) : [];
185
- if (related.length > 0) {
186
- lines.push("", "Related commands:");
187
- for (const command of related)
188
- lines.push(` ${command}`);
189
- }
190
- return `${lines.join("\n")}\n`;
191
- }
192
- function parseOutputOptions(args) {
193
- const stripped = [];
194
- let json = false;
195
- let progressJsonl = false;
196
- let responseFile = null;
197
- for (let index = 0; index < args.length; index += 1) {
198
- const arg = args[index];
199
- if (arg === undefined)
200
- continue;
201
- if (arg === "--json") {
202
- json = true;
203
- continue;
204
- }
205
- if (arg === "--progress-jsonl") {
206
- progressJsonl = true;
207
- continue;
208
- }
209
- if (arg === "--response-file") {
210
- const file = args[++index];
211
- if (!file)
212
- throw new AppError("usage", "--response-file requires a path", 2);
213
- responseFile = file;
214
- continue;
215
- }
216
- stripped.push(arg);
217
- }
218
- if (responseFile && !json)
219
- throw new AppError("usage", "--response-file requires --json", 2);
220
- const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
221
- return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
222
- }
223
- /** Persist the complete machine response for harnesses that cap tool output.
224
- * The visible response keeps routing fields but omits the potentially large
225
- * rendered prompt; an agent can safely read the named file without rerunning
226
- * the lifecycle command. */
227
- function writeResponseFile(payload, output) {
228
- if (!output.responseFile)
229
- return payload;
230
- const file = path.resolve(output.responseFile);
231
- try {
232
- fs.mkdirSync(path.dirname(file), { recursive: true });
233
- const temporary = `${file}.tmp-${process.pid}-${crypto.randomUUID()}`;
234
- fs.writeFileSync(temporary, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
235
- fs.renameSync(temporary, file);
236
- }
237
- catch (error) {
238
- // The lifecycle mutation may already be accepted. Do not retry it just
239
- // because its optional full-response file could not be written.
240
- return { ...(payload && typeof payload === "object" && !Array.isArray(payload) ? payload : { ok: true, result: payload }), response_file: file, response_file_error: String(error) };
241
- }
242
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
243
- return { ok: true, response_file: file };
244
- const visible = { ...payload, response_file: file };
245
- delete visible.worker_prompt_markdown;
246
- return visible;
247
- }
248
590
  function progressForCommand(args) {
249
591
  const [family, command, subcommand] = args;
250
592
  if (family === "merge-queue" && command === "wait-next") {
@@ -500,27 +842,676 @@ function stringValue(value) {
500
842
  function shortSha(value) {
501
843
  return value ? value.slice(0, 12) : "unknown";
502
844
  }
503
- async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args)) {
504
- const [family, command, ...rest] = args;
505
- const parsed = parseArgs(rest);
845
+ /** All caller supplied file/stdin data is consumed here, before a managed
846
+ * invocation is allowed to claim execution or a stage claims its hook event. */
847
+ async function prepareCliInput(...args) {
848
+ try {
849
+ return await prepareCliInputValue(...args);
850
+ }
851
+ catch (error) {
852
+ return prepareError(error);
853
+ }
854
+ }
855
+ async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
856
+ const { family, command, parsed } = route;
857
+ const prepared = { route };
858
+ if (optionalOption(parsed, "reason-file")) {
859
+ const value = prepareJsonFile(requiredOption(parsed, "reason-file"), "Check recovery evidence").value;
860
+ if (!value || typeof value.reason !== "string" || !value.reason.trim() || Object.keys(value).some(key => key !== "reason"))
861
+ throw new AppError("validation", "Recovery evidence must be a JSON object with one nonempty reason", 2);
862
+ prepared.retryReason = value.reason;
863
+ }
864
+ else if (optionalOption(parsed, "retry-check"))
865
+ prepared.retryReason = optionalOption(parsed, "reason");
866
+ if (route.key === "project register")
867
+ resolveProjectRoot(requiredOption(parsed, "root"));
868
+ if (route.key === "project archive")
869
+ prepareProjectArchive(context, { idOrAlias: parsed.positional[0], root: optionalOption(parsed, "root"), reason: requiredOption(parsed, "reason") });
870
+ if (route.key === "protocol register")
871
+ prepareProtocolRegistration(context, { handshakeId: requiredPosition(parsed, 0, "handshake-id"), projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "workspace-path") });
872
+ if (route.key === "canon register")
873
+ prepared.canonRegistration = prepareCanonRegistration(context, { root: requiredOption(parsed, "root") });
874
+ if (route.key === "run attach-stage")
875
+ prepareFlowRunStageAttachment(context, stageAttachmentInput(parsed));
876
+ if (route.key === "run complete-stage")
877
+ prepareRawFlowRunStageCompletion(context, stageCompletionInput(parsed));
878
+ if (route.key === "run override")
879
+ prepareFlowRunCompletion(context, runOverrideInput(parsed));
880
+ if (route.key === "engine bind-run")
881
+ prepared.engineBind = await prepareRunEngineBind(context, engineBindInput(parsed));
882
+ if (route.key === "worktree create")
883
+ prepared.worktreeCreate = prepareWorktreeCreate(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree create"), branch: requiredOption(parsed, "branch"), base: requiredOption(parsed, "base"), path: requiredOption(parsed, "path") });
884
+ if (route.key === "worktree bootstrap")
885
+ prepared.worktreeBootstrap = prepareWorktreeBootstrap(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree bootstrap") });
886
+ if (route.key === "worktree close")
887
+ prepareWorktreeClose(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree close"), mode: requiredOption(parsed, "mode") });
888
+ if (route.key.startsWith("plan item "))
889
+ preparePlanItemMutation(context, planItemInput(parsed, requiredScopeProjectRoot(scopeProjectRoot, route.key)));
890
+ if (route.key === "lane workspace set")
891
+ prepareLaneWorkspace(context, laneWorkspaceInput(parsed));
892
+ if (route.key === "lane waiter cancel")
893
+ prepareLaneWaiterCancellation(context, laneWaiterCancelInput(parsed));
894
+ if (route.key === "merge-queue complete")
895
+ prepareMergeJobCompletion(context, { ...mergeJobTarget(parsed, scopeProjectRoot), summary: requiredOption(parsed, "summary") });
896
+ if (route.key === "merge-queue cancel")
897
+ prepareMergeQueueCancellation(context, mergeCancellationInput(parsed, scopeProjectRoot));
898
+ if (route.key === "merge-queue wait-next")
899
+ prepareMergeQueueWait(context, mergeWaitInput(parsed, scopeProjectRoot));
900
+ if (route.key === "merge-queue next" || route.key === "merge-queue claim")
901
+ prepareMergeClaim(context, { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed), ...(route.key === "merge-queue claim" ? { protocolId: requiredPosition(parsed, 0, "protocol-id") } : {}) });
902
+ if (route.key === "merge-queue note")
903
+ prepareMergeJobNote(context, { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
904
+ if (route.key === "merge bundle complete")
905
+ prepareMergeBundleCompletion(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
906
+ if (route.key === "merge bundle claim")
907
+ prepareMergeBundleClaim(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed) });
908
+ if (route.key === "merge bundle fail")
909
+ prepareMergeBundleFailure(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
910
+ if (route.key === "merge-queue fail")
911
+ prepareMergeJobFailure(context, { ...mergeJobTarget(parsed, scopeProjectRoot), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
912
+ if (route.key.startsWith("lane lock ") && route.key !== "lane lock status") {
913
+ const action = requiredPosition(parsed, 0, "lane lock action");
914
+ prepareLaneLock(context, {
915
+ projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"),
916
+ workspacePath: workspacePathOption(parsed), leaseToken: optionalOption(parsed, "lease-token"), reason: optionalOption(parsed, "reason"),
917
+ ttlSeconds: optionalNumber(parsed, "ttl"), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
918
+ }, action);
919
+ }
920
+ if (route.key === "run recovery begin")
921
+ prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "interruption-json"), "interruption");
922
+ if (route.key === "run recovery seal")
923
+ prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "settlement-json"), "settlement");
924
+ if (route.key === "run recovery capture")
925
+ prepared.recoveryCapture = prepareRecoveryCapture(context, recoveryCaptureInput(parsed));
926
+ if (route.key === "run recovery resume")
927
+ prepared.recoveryResume = prepareRunRecoveryResume(context, recoveryResumeInput(parsed));
928
+ if (route.key === "run drive answer")
929
+ prepared.controllerAnswer = prepareRunControllerAnswer(context, controllerAnswerInput(parsed));
930
+ if (route.key === "run drive launch")
931
+ prepared.controllerLaunch = prepareRunControllerLaunch(context, controllerLaunchInput(parsed));
932
+ if (route.key === "run drive context")
933
+ prepared.controllerContext = prepareRunControllerContext(context, controllerContextInput(parsed));
934
+ if (route.key === "protocol transition" || route.key === "transition")
935
+ prepared.protocolTransition = prepareProtocolTransition(context, protocolTransitionInput(parsed, scopeProjectRoot ?? resolveOperationProjectRoot(context, args)));
936
+ if (route.key === "codex home init")
937
+ prepared.codexHome = prepareCodexHome(context, codexHomeInput(parsed));
938
+ if (route.key === "codex home remove")
939
+ prepareCodexHomeRemoval(context, { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), mode: requiredOption(parsed, "mode") });
940
+ if (["codex hooks install", "codex hooks remove"].includes(route.key))
941
+ prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
942
+ if (route.key === "migration apply")
943
+ prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
944
+ if (route.key === "migration verify")
945
+ prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
946
+ if (responseFile)
947
+ prepareResponseDestination(responseFile);
948
+ if (family === "dashboard") {
949
+ const action = dashboardAction(command);
950
+ prepareDashboardOptions({ format: optionalOption(parsed, "format"), viewer: optionalOption(parsed, "viewer"), open: optionalOption(parsed, "open") });
951
+ const target = resolveDashboardTarget(context, { action, project: optionalOption(parsed, "project"), projectRoot: optionalOption(parsed, "project-root"), protocol: optionalOption(parsed, "protocol"), all: hasOption(parsed, "all"), global: hasOption(parsed, "global") || command?.endsWith("-global") });
952
+ if (action === "refresh" && target.kind !== "global" && optionalOption(parsed, "output") !== undefined) {
953
+ throw new AppError("validation", "--output is supported only for global dashboard refresh; use dashboard render for a custom project/protocol output", 2, { parameter: "output" });
954
+ }
955
+ if (target.kind === "protocol")
956
+ requireProtocol(context, target.protocolId, target.project.id);
957
+ if (action !== "data") {
958
+ if (target.kind === "global" || target.kind === "all_projects") {
959
+ prepareGlobalDashboardRender(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? (command?.endsWith("-global") ? undefined : "html") });
960
+ }
961
+ else {
962
+ const format = optionalOption(parsed, "format") ?? (action === "open" || hasOption(parsed, "project") ? "html" : undefined);
963
+ const input = { projectRoot: target.project.root, format, ...(target.kind === "protocol" ? { protocol: target.protocolId } : {}) };
964
+ if (action === "refresh")
965
+ prepareDashboardRefresh(context, { ...input, open: optionalOption(parsed, "open") });
966
+ else if (action === "render")
967
+ prepareDashboardRender(context, { ...input, output: optionalOption(parsed, "output") });
968
+ else if (target.kind === "protocol") {
969
+ if (format !== "html")
970
+ throw new AppError("validation", "Protocol dashboard open supports --format html", 2, { protocol: target.protocolId });
971
+ prepareDashboardRender(context, input);
972
+ }
973
+ }
974
+ }
975
+ }
976
+ if (route.key === "prompt render") {
977
+ const input = workerPromptInput(parsed);
978
+ prepared.workerPrompt = { input, value: prepareWorkerPrompt(context, input) };
979
+ }
980
+ if (route.key === "run snapshot restore" || route.key === "run snapshot bootstrap restore") {
981
+ try {
982
+ const input = snapshotRestoreInput(parsed);
983
+ if (route.key === "run snapshot restore")
984
+ prepared.snapshotRestore = prepareEvalRunRestore(context, input);
985
+ else
986
+ prepared.bootstrapRestore = prepareEvalBootstrapRestore(input);
987
+ }
988
+ catch (error) {
989
+ return prepareError(error, { parameter: "snapshot/project-root" });
990
+ }
991
+ }
992
+ if (route.key === "run snapshot bootstrap create") {
993
+ try {
994
+ prepareEvalBootstrapSnapshot(context, { projectRoot: requiredOption(parsed, "project-root"), output: requiredOption(parsed, "output"), flow: optionalOption(parsed, "flow") });
995
+ }
996
+ catch (error) {
997
+ return prepareError(error, { parameter: "project-root/output" });
998
+ }
999
+ }
1000
+ if (route.key === "run snapshot create") {
1001
+ prepareEvalRunSnapshotCreation(context, snapshotCreateInput(parsed));
1002
+ }
1003
+ if (route.key === "run start") {
1004
+ try {
1005
+ const input = runStartInput(parsed);
1006
+ prepared.runStart = { input, value: prepareFlowRunStart(context, input) };
1007
+ }
1008
+ catch (error) {
1009
+ return prepareError(error, { parameter: "run start" });
1010
+ }
1011
+ }
1012
+ if (route.key === "run fork") {
1013
+ try {
1014
+ prepared.runFork = prepareRunFork(runForkInput(parsed));
1015
+ }
1016
+ catch (error) {
1017
+ return prepareError(error, { parameter: "from/output" });
1018
+ }
1019
+ }
1020
+ if (["runtime scope fence", "runtime scope stop"].includes(route.key))
1021
+ prepareRuntimeScopeFence(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"));
1022
+ if (route.key === "runtime scope resume")
1023
+ prepareRuntimeScopeResumeRequest(context, runtimeScopeResumeInput(parsed));
1024
+ if (route.key === "runtime scope reconcile")
1025
+ prepareRuntimeScopeReconciliation(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id"));
1026
+ if (route.key === "runtime scope serve")
1027
+ prepareRuntimeScopeServing(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")));
1028
+ if (route.key === "runtime scope control") {
1029
+ try {
1030
+ const raw = optionalOption(parsed, "manifest-json");
1031
+ prepared.runtimeManifest = prepareRuntimeScopeControl(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), requiredOption(parsed, "mode"), raw === undefined ? undefined : prepareJsonText(raw, { file: "<manifest-json>", label: "scope manifest" }).value, context);
1032
+ }
1033
+ catch (error) {
1034
+ return prepareError(error, { parameter: "scope-id/request-id/manifest-json" });
1035
+ }
1036
+ }
1037
+ if (family === "runtime" && command === "process")
1038
+ Object.assign(prepared, prepareRuntimeProcess(context, route.key, parsed));
1039
+ if (route.key === "runtime process start") {
1040
+ try {
1041
+ prepareRuntimeService(context, runtimeServiceInput(parsed));
1042
+ }
1043
+ catch (error) {
1044
+ return prepareError(error, { parameter: "runtime process start" });
1045
+ }
1046
+ }
1047
+ if (family === "work" && command === "retry") {
1048
+ try {
1049
+ prepareWorkRetry(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
1050
+ }
1051
+ catch (error) {
1052
+ return prepareError(error, { parameter: "work-id/reason" });
1053
+ }
1054
+ }
1055
+ if (family === "work" && (command === "fail" || command === "cancel")) {
1056
+ try {
1057
+ prepareWorkSettlement(context, requiredPosition(parsed, 0, "work-id"), command === "fail" ? "failed" : "cancelled", requiredOption(parsed, "reason"));
1058
+ }
1059
+ catch (error) {
1060
+ return prepareError(error, { parameter: "work-id/reason" });
1061
+ }
1062
+ }
1063
+ if (family === "work" && (command === "deps" || command === "delete")) {
1064
+ try {
1065
+ if (command === "delete")
1066
+ prepareWorkDelete(context, requiredPosition(parsed, 0, "work-id"));
1067
+ else
1068
+ prepareWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), on: parsed.options.get("on") ?? [] });
1069
+ }
1070
+ catch (error) {
1071
+ return prepareError(error, { parameter: "work-id/on" });
1072
+ }
1073
+ }
1074
+ if (route.key === "cleanup apply") {
1075
+ const planFile = requiredOption(parsed, "plan-file");
1076
+ try {
1077
+ prepared.cleanup = prepareCleanupApply(context, { projectRoot: requiredOption(parsed, "project-root"), planFile, reason: requiredOption(parsed, "reason"), force: hasOption(parsed, "force") });
1078
+ }
1079
+ catch (error) {
1080
+ return prepareError(error, { parameter: "plan-file", file: planFile });
1081
+ }
1082
+ }
1083
+ if (route.key === "stage block" || route.key === "stage unblock") {
1084
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
1085
+ try {
1086
+ if (route.key === "stage block") {
1087
+ const summaryFile = optionalOption(parsed, "summary-file"), summaryStdin = hasOption(parsed, "summary-stdin");
1088
+ if (Boolean(summaryFile) === summaryStdin)
1089
+ throw new AppError("usage", "stage block requires exactly one of --summary-file or --summary-stdin", 2);
1090
+ prepared.interactionText = summaryFile ? prepareTextFile(summaryFile, "Runtime block summary").text : await readStdin(io.stdin);
1091
+ prepareStageRuntimeBlock(context, { ...input, kind: requiredOption(parsed, "kind"), code: requiredOption(parsed, "code"), summary: prepared.interactionText, retryable: hasOption(parsed, "retryable") });
1092
+ }
1093
+ else
1094
+ prepareStageRuntimeUnblock(context, input);
1095
+ }
1096
+ catch (error) {
1097
+ return prepareError(error, { parameter: "stage/work/summary" });
1098
+ }
1099
+ }
1100
+ if (route.key === "zcode invocation prepare") {
1101
+ const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label: "invocation issuance" }).value;
1102
+ const payload = value && typeof value === "object" ? value : {};
1103
+ try {
1104
+ prepared.lifecycleIssuance = prepareLifecycleIssuance(payload.command, payload.scope);
1105
+ }
1106
+ catch (error) {
1107
+ if (error instanceof AppError && ["invocation_command_invalid", "invocation_scope_invalid"].includes(error.code))
1108
+ return prepareError(new AppError("usage", error.message, 2, { cause_code: error.code }), { parameter: "stdin" });
1109
+ throw error;
1110
+ }
1111
+ assertLifecycleInvocationCurrent(context, prepared.lifecycleIssuance.scope, prepared.lifecycleIssuance.command);
1112
+ }
1113
+ if (route.key === "work add-batch") {
1114
+ const file = requiredOption(parsed, "file");
1115
+ try {
1116
+ prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file,
1117
+ ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}),
1118
+ ...(optionalOption(parsed, "request-key") ? { requestKey: optionalOption(parsed, "request-key") } : {}) });
1119
+ }
1120
+ catch (error) {
1121
+ return prepareError(error, { parameter: "file/parent", file });
1122
+ }
1123
+ }
1124
+ if (route.key === "work repair add") {
1125
+ const taskFile = optionalOption(parsed, "task-file"), taskStdin = hasOption(parsed, "task-stdin");
1126
+ if (Boolean(taskFile) === taskStdin)
1127
+ throw new AppError("usage", "work repair add requires exactly one of --task-file or --task-stdin", 2);
1128
+ if (taskStdin && io.stdin?.isTTY)
1129
+ throw new AppError("usage", "--task-stdin requires piped or redirected input", 2);
1130
+ const task = taskFile ? prepareTextFile(taskFile, "Repair task").text : await readStdin(io.stdin);
1131
+ prepared.workRepair = prepareCliWorkRepair(context, parsed, task, scopeProjectRoot);
1132
+ }
1133
+ if (route.key === "run config set" || route.key === "run vars set") {
1134
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") };
1135
+ try {
1136
+ if (route.key === "run config set")
1137
+ prepareFlowRunConfig(context, { ...input, reason: requiredOption(parsed, "reason") });
1138
+ else
1139
+ prepareFlowRunVariable(context, input);
1140
+ }
1141
+ catch (error) {
1142
+ return prepareError(error, { parameter: "run config/vars" });
1143
+ }
1144
+ }
1145
+ if (route.key === "schema validate") {
1146
+ try {
1147
+ prepared.schemaValidation = dispatchSchema(context, command, parsed);
1148
+ }
1149
+ catch (error) {
1150
+ return prepareError(error, { parameter: "file", file: requiredOption(parsed, "file") });
1151
+ }
1152
+ }
1153
+ if (route.key === "project config set") {
1154
+ try {
1155
+ prepareProjectConfigValue(requiredOption(parsed, "key"), requiredOption(parsed, "value"));
1156
+ }
1157
+ catch (error) {
1158
+ return prepareError(error, { parameter: "key/value" });
1159
+ }
1160
+ }
1161
+ if (family === "session" && command === "register") {
1162
+ try {
1163
+ prepared.sessionPayload = prepareFlowSessionPayload({ payloadBase64: optionalOption(parsed, "payload-base64"), payloadFile: optionalOption(parsed, "payload-file"), payloadJson: optionalOption(parsed, "payload-json") });
1164
+ }
1165
+ catch (error) {
1166
+ return prepareError(error, { parameter: "session payload" });
1167
+ }
1168
+ }
1169
+ const run = (projectRoot, runId) => {
1170
+ try {
1171
+ const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
1172
+ const row = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
1173
+ if (!row)
1174
+ throw new AppError("not_found", "RUN is not registered", 2, { run_id: runId });
1175
+ if (!row.run_root)
1176
+ throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId });
1177
+ return { runRoot: row.run_root, workspaceRoot: row.workspace_root, indexJson: row.index_json };
1178
+ }
1179
+ catch (error) {
1180
+ return prepareError(error, { parameter: "run-id", run_id: runId });
1181
+ }
1182
+ };
1183
+ const json = (file, stdin, label, schema) => {
1184
+ if (stdin) {
1185
+ // The caller awaits this helper; the synchronous facade below is only
1186
+ // used for file inputs. Keep stdin handling in the stage branch.
1187
+ throw new AppError("usage", `${label} stdin preparation must be awaited`, 2);
1188
+ }
1189
+ if (!file)
1190
+ throw new AppError("usage", `${label} input is required`, 2);
1191
+ return prepareJsonSchema(file, label, schema);
1192
+ };
1193
+ if (family === "stage" && command === "start") {
1194
+ const stage = requiredOption(parsed, "stage");
1195
+ if (stage !== "specify" && ["bootstrap", "intake-file", "intake-stdin"].some(option => hasOption(parsed, option)))
1196
+ throw new AppError("usage", "--bootstrap and intake inputs are only supported for SPECIFY", 2);
1197
+ requiredOption(parsed, "project-root");
1198
+ if (!hasOption(parsed, "bootstrap"))
1199
+ requiredPosition(parsed, 0, "run-id");
1200
+ if (hasOption(parsed, "session-id"))
1201
+ throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
1202
+ const contextFile = optionalOption(parsed, "context-file"), contextSha256 = optionalOption(parsed, "context-sha256");
1203
+ if (Boolean(contextFile) !== Boolean(contextSha256))
1204
+ throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
1205
+ if ([hasOption(parsed, "intake-file"), hasOption(parsed, "intake-stdin"), Boolean(contextFile)].filter(Boolean).length > 1)
1206
+ throw new AppError("usage", "Use exactly one of --intake-file, --intake-stdin or --context-file", 2);
1207
+ if (!hasOption(parsed, "bootstrap")) {
1208
+ const bundle = readFlowRunExecutionBundle(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") });
1209
+ if (bundle)
1210
+ prepared.stageBindingBundle = { stage, bundle, ...(contextSha256 ? { contextSha256 } : {}) };
1211
+ if (bundle?.descriptor.descriptor.id === "mb-init") {
1212
+ bundle.descriptor.stage(stage);
1213
+ if (hasOption(parsed, "dir"))
1214
+ throw new AppError("usage", "Memory stage layout belongs to its frozen descriptor", 2);
1215
+ const loaded = contextFile && contextSha256 ? loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) : undefined;
1216
+ if (loaded)
1217
+ prepared.stageStart = { externalContext: loaded };
1218
+ prepared.memoryStage = { operation: "start", bytes: loaded ? Buffer.from(loaded.bytes) : Buffer.alloc(0) };
1219
+ return prepared;
1220
+ }
1221
+ }
1222
+ if (contextFile && contextSha256) {
1223
+ try {
1224
+ prepared.stageStart = { externalContext: loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) };
1225
+ }
1226
+ catch (error) {
1227
+ return prepareError(error, { file: contextFile, parameter: "context-file" });
1228
+ }
1229
+ if (!hasOption(parsed, "bootstrap")) {
1230
+ const { runRoot } = run(requiredOption(parsed, "project-root"), requiredPosition(parsed, 0, "run-id"));
1231
+ prepareExternalStageContext({ stageRoot: path.join(runRoot, vnextStageDirectory(stage)), loaded: prepared.stageStart.externalContext });
1232
+ }
1233
+ }
1234
+ if (stage === "specify") {
1235
+ const intakeFile = optionalOption(parsed, "intake-file");
1236
+ if (intakeFile)
1237
+ prepared.stageStart = { intakeMarkdown: prepareTextFile(intakeFile, "SPECIFY intake").text };
1238
+ else if (hasOption(parsed, "intake-stdin"))
1239
+ prepared.stageStart = { intakeMarkdown: await readStdin(io.stdin) };
1240
+ else if (prepared.stageStart?.externalContext)
1241
+ prepared.stageStart.intakeMarkdown = runnerIntake(prepared.stageStart.externalContext);
1242
+ const projectRoot = requiredOption(parsed, "project-root");
1243
+ const bootstrap = hasOption(parsed, "bootstrap");
1244
+ if (isVnextSpecifyFlow(projectRoot) && (bootstrap || isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) {
1245
+ prepared.specifyStart = prepareVnextSpecifyStart(context, { projectRoot, ...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : {}), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}), slug: optionalOption(parsed, "subject"), ...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {}) });
1246
+ }
1247
+ else if (bootstrap) {
1248
+ if (hasOption(parsed, "intake-stdin") || contextFile)
1249
+ throw new AppError("usage", "Legacy SPECIFY bootstrap supports --intake-file, not stdin or runner context", 2);
1250
+ prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot, stage, bootstrap: { subject: requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) }, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) }, prepared.stageStart?.intakeMarkdown);
1251
+ }
1252
+ }
1253
+ if (stage === "merge" && isVnextMergeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1254
+ prepared.mergeStart = prepareVnextMergeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1255
+ }
1256
+ if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1257
+ prepared.protocolizeStart = prepareVnextProtocolizeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1258
+ }
1259
+ if (stage === "plan" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1260
+ prepared.planStart = prepareVnextPlanStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1261
+ }
1262
+ if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1263
+ prepared.planReviewStart = prepareVnextPlanReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1264
+ }
1265
+ if (stage === "code" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1266
+ prepared.codeStart = prepareVnextCodeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1267
+ }
1268
+ if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1269
+ prepared.codeReviewStart = prepareVnextCodeReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1270
+ }
1271
+ if (![prepared.specifyStart, prepared.protocolizeStart, prepared.planStart, prepared.planReviewStart, prepared.codeStart, prepared.codeReviewStart, prepared.mergeStart, prepared.legacyStageStart].some(Boolean)) {
1272
+ if (prepared.stageStart?.externalContext)
1273
+ throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
1274
+ prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) });
1275
+ }
1276
+ }
1277
+ if (family === "stage" && (command === "pause" || command === "resume")) {
1278
+ requiredPosition(parsed, 0, "run-id");
1279
+ requiredOption(parsed, "project-root");
1280
+ requiredOption(parsed, "stage");
1281
+ requiredOption(parsed, "work");
1282
+ const fileParameter = command === "pause" ? "question-file" : "answer-file";
1283
+ const stdinParameter = command === "pause" ? "question-stdin" : "answer-stdin";
1284
+ const inputFile = optionalOption(parsed, fileParameter);
1285
+ const fromStdin = hasOption(parsed, stdinParameter);
1286
+ if (Boolean(inputFile) === fromStdin)
1287
+ throw new AppError("usage", `stage ${command} requires exactly one of --${fileParameter} or --${stdinParameter}`, 2);
1288
+ if (fromStdin && io.stdin?.isTTY)
1289
+ throw new AppError("usage", `--${stdinParameter} requires piped or redirected input`, 2);
1290
+ prepared.interactionText = inputFile ? prepareTextFile(inputFile, command === "pause" ? "User question" : "User answer").text : await readStdin(io.stdin);
1291
+ if (!prepared.interactionText.trim())
1292
+ return prepareError(new AppError("validation", `stage ${command} requires non-empty input`, 2), { parameter: inputFile ? fileParameter : stdinParameter });
1293
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
1294
+ try {
1295
+ if (command === "pause")
1296
+ prepareStagePause(context, { ...input, question: prepared.interactionText });
1297
+ else
1298
+ prepared.resumeEvidence = prepareStageResume(context, { ...input, answer: prepared.interactionText }).evidence;
1299
+ }
1300
+ catch (error) {
1301
+ return prepareError(error, { parameter: "stage/work" });
1302
+ }
1303
+ }
1304
+ if (family === "work" && command === "finish") {
1305
+ const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
1306
+ if (Boolean(retryReceiptId) !== Boolean(retryReason))
1307
+ throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
1308
+ const resultFile = optionalOption(parsed, "result-file");
1309
+ const resultStdin = hasOption(parsed, "result-stdin");
1310
+ if (Boolean(resultFile) === resultStdin)
1311
+ throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
1312
+ const maxBytes = workResultByteLimit(context, requiredPosition(parsed, 0, "work-id"));
1313
+ prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result", maxBytes).text : await readStdin(io.stdin, maxBytes) };
1314
+ prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
1315
+ }
1316
+ if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
1317
+ prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
1318
+ }
1319
+ if (family === "stage" && command === "native" && requiredPosition(parsed, 0, "native action") === "observe") {
1320
+ prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
1321
+ }
1322
+ if (family === "memory" && (["compose", "corrections"].includes(command ?? "") && parsed.positional[0] === "register"
1323
+ || command === "repair" && parsed.positional[0] === "publication-reserve")) {
1324
+ const bytes = prepareFileBytes(requiredOption(parsed, "result-file"), `Memory ${command} result`, 1024 * 1024).bytes;
1325
+ if (!bytes.length || bytes.length > 1024 * 1024)
1326
+ throw new AppError("usage", "Memory operation result exceeds its byte bound", 2);
1327
+ prepared.memoryOperationBytes = bytes;
1328
+ }
1329
+ if (family !== "stage" || command !== "finish")
1330
+ return prepared;
1331
+ const projectRoot = requiredOption(parsed, "project-root");
1332
+ const runId = requiredPosition(parsed, 0, "run-id");
1333
+ const stage = requiredOption(parsed, "stage");
1334
+ if (hasOption(parsed, "status"))
1335
+ throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
1336
+ if (["result-file", "semantic-file", "data", "result-stdin"].filter(key => hasOption(parsed, key)).length > 1)
1337
+ throw new AppError("usage", "Use exactly one result input: --result-file, --semantic-file, --data or --result-stdin", 2);
1338
+ if (Boolean(optionalOption(parsed, "retry-check")) !== Boolean(prepared.retryReason))
1339
+ throw new AppError("usage", "--retry-check requires recovery evidence", 2);
1340
+ const resultStdin = hasOption(parsed, "result-stdin");
1341
+ const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
1342
+ const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
1343
+ const runtime = run(projectRoot, runId);
1344
+ if (JSON.parse(runtime.indexJson).execution_bundle?.flow_id === "mb-init") {
1345
+ const bundle = readFlowRunExecutionBundle(context, { projectRoot, runId });
1346
+ if (!bundle || bundle.descriptor.descriptor.id !== "mb-init")
1347
+ throw new AppError("cli_input_unprepared", "Memory RUN bundle is unavailable", 1);
1348
+ bundle.descriptor.stage(stage);
1349
+ if (["dir", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"].some(option => hasOption(parsed, option)))
1350
+ throw new AppError("usage", "Memory stage finish requires its registered JSON result, not legacy result options", 2);
1351
+ if (Boolean(resultFile) === resultStdin)
1352
+ throw new AppError("usage", "Memory stage finish requires exactly one result input", 2);
1353
+ const bytes = resultStdin ? Buffer.from(await readStdin(io.stdin, 1024 * 1024)) : prepareFileBytes(resultFile, "Memory stage result", 1024 * 1024).bytes;
1354
+ if (!bytes.length || bytes.length > 1024 * 1024)
1355
+ throw new AppError("usage", "Memory stage result exceeds its byte bound", 2);
1356
+ prepared.memoryStage = { operation: "finish", bytes };
1357
+ return prepared;
1358
+ }
1359
+ const terminalReplay = (name) => {
1360
+ let index;
1361
+ try {
1362
+ index = JSON.parse(runtime.indexJson);
1363
+ }
1364
+ catch (error) {
1365
+ throw new AppError("runtime_artifact_invalid", "RUN index is not valid retained JSON", 1, { run_id: runId, cause: error instanceof Error ? error.message : String(error) });
1366
+ }
1367
+ return index.stage_runs?.some(entry => entry.stage === name && entry.status === "done") === true
1368
+ && fs.existsSync(path.join(runtime.runRoot, vnextStageDirectory(name), "stage-report.json"));
1369
+ };
1370
+ const acceptStageInputs = (allowed) => {
1371
+ const options = allowed.split(" ");
1372
+ for (const name of ["dir", "semantic-file", "data", "result-file", "result-stdin", "outcome", "decision-file", "verification-file", "retry-check", "reason", "reason-file", "request", "work"]) {
1373
+ if (hasOption(parsed, name) && !options.includes(name === "reason-file" ? "reason" : name))
1374
+ throw new AppError("usage", `stage finish ${stage} does not accept --${name} for this flow`, 2, { parameter: name });
1375
+ }
1376
+ };
1377
+ const fromStdin = async (label, schema) => {
1378
+ const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label });
1379
+ try {
1380
+ validateSchema({ ...schema, file: value.file, data: value.value });
1381
+ }
1382
+ catch (error) {
1383
+ return prepareError(error, { file: value.file, label });
1384
+ }
1385
+ return value;
1386
+ };
1387
+ if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
1388
+ acceptStageInputs("result-file semantic-file data result-stdin outcome");
1389
+ const outcome = optionalOption(parsed, "outcome");
1390
+ if (!outcome || !["specified", "failed", "cancelled"].includes(outcome))
1391
+ throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
1392
+ if (Boolean(resultFile) === resultStdin)
1393
+ throw new AppError("usage", "vNext SPECIFY finish requires exactly one result input", 2);
1394
+ const schema = { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot };
1395
+ const input = resultStdin ? await fromStdin("SPECIFY result", schema) : json(resultFile, false, "SPECIFY result", schema);
1396
+ prepared.stageFinish = { value: input.value, text: input.text };
1397
+ }
1398
+ else if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
1399
+ acceptStageInputs("result-file semantic-file data");
1400
+ if (!resultFile || resultStdin || hasOption(parsed, "outcome"))
1401
+ throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
1402
+ let absolute;
1403
+ try {
1404
+ absolute = prepareProtocolizeResultPath(runtime.runRoot, resultFile);
1405
+ }
1406
+ catch (error) {
1407
+ return prepareError(error, { file: resultFile });
1408
+ }
1409
+ const input = json(absolute, false, "PROTOCOLIZE result", { schemaName: "vnext-protocolize-result", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1410
+ prepared.stageFinish = { value: input.value, text: input.text };
1411
+ }
1412
+ else if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
1413
+ acceptStageInputs("");
1414
+ if (hasOption(parsed, "code-work-batch-file"))
1415
+ throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
1416
+ try {
1417
+ prepared.planFinish = prepareVnextPlanFinish(context, { projectRoot, runId });
1418
+ }
1419
+ catch (error) {
1420
+ return prepareError(error, { parameter: "PLAN artifacts" });
1421
+ }
1422
+ }
1423
+ else if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
1424
+ acceptStageInputs("request work retry-check reason");
1425
+ if (resultStdin || resultFile || hasOption(parsed, "outcome"))
1426
+ throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
1427
+ requiredOption(parsed, "request");
1428
+ requiredOption(parsed, "work");
1429
+ try {
1430
+ prepared.mergeFinish = prepareVnextMergeFinish(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(optionalOption(parsed, "retry-check") ? { retryCheckId: optionalOption(parsed, "retry-check"), retryReason: prepared.retryReason } : {}) });
1431
+ }
1432
+ catch (error) {
1433
+ return prepareError(error, { parameter: "MERGE finish" });
1434
+ }
1435
+ }
1436
+ else if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
1437
+ acceptStageInputs("decision-file");
1438
+ if (resultStdin || hasOption(parsed, "outcome"))
1439
+ throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
1440
+ const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "04-plan-review", "decision.json");
1441
+ const input = json(decision, false, "PLAN-REVIEW decision", { schemaName: "plan-review-decision", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1442
+ prepared.stageFinish = { value: input.value, text: input.text };
1443
+ }
1444
+ else if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
1445
+ acceptStageInputs("verification-file retry-check reason");
1446
+ if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome"))
1447
+ throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
1448
+ const verification = optionalOption(parsed, "verification-file");
1449
+ if (!verification)
1450
+ throw new AppError("usage", "vNext CODE finish requires --verification-file", 2);
1451
+ if (!terminalReplay(stage)) {
1452
+ const input = json(verification, false, "CODE verification", { schemaName: "code-verification", projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1453
+ prepared.stageFinish = { value: input.value, text: input.text };
1454
+ }
1455
+ }
1456
+ else if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
1457
+ acceptStageInputs("decision-file retry-check reason");
1458
+ if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
1459
+ throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
1460
+ const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "06-code-review", "decision.json");
1461
+ if (!terminalReplay(stage)) {
1462
+ const input = prepareCodeReviewDecision(runtime.runRoot, optionalOption(parsed, "decision-file"));
1463
+ try {
1464
+ validateSchema({ schemaName: "code-review-decision", file: decision, data: input.value, projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1465
+ }
1466
+ catch (error) {
1467
+ return prepareError(error, { file: decision, label: "CODE-REVIEW decision" });
1468
+ }
1469
+ prepared.stageFinish = input;
1470
+ }
1471
+ }
1472
+ else {
1473
+ acceptStageInputs("dir semantic-file data");
1474
+ if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin)
1475
+ throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
1476
+ prepared.legacyStageFinish = prepareStageFinish(context, { projectRoot, runId, stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}), ...(semanticFile ? { semanticFile } : {}) });
1477
+ }
1478
+ return prepared;
1479
+ }
1480
+ async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args), prepared = {}) {
1481
+ if (!prepared.route)
1482
+ throw new AppError("command_not_prepared", "Command dispatch requires prepared arguments", 1);
1483
+ const { family, command, parsed } = prepared.route;
1484
+ if (family === "zcode" && command === "invocation" && parsed.positional[0] === "prepare") {
1485
+ const payload = prepared.lifecycleIssuance;
1486
+ if (!payload)
1487
+ throw new AppError("command_not_prepared", "Invocation issuance requires prepared input", 1);
1488
+ assertLifecycleInvocationCurrent(context, payload.scope, payload.command);
1489
+ return { ok: true, ...issueLifecycleInvocation(context, payload.command, payload.scope) };
1490
+ }
1491
+ if (family === "storage" && command === "migrate-writer") {
1492
+ if (parsed.positional.length || [...parsed.options.keys()].some(key => !["database", "backup", "offline"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
1493
+ throw new AppError("usage", "Usage: dd-flow storage migrate-writer --database ABSOLUTE_DB --backup NEW_ABSOLUTE_FILE --offline", 2);
1494
+ if (parsed.options.get("offline")?.[0] !== flagValue)
1495
+ throw new AppError("usage", "Writer migration requires --offline after draining all local and external runtime owners", 2);
1496
+ return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
1497
+ }
506
1498
  if (family === "engine") {
507
- return dispatchEngine(context, command, parsed, classification);
1499
+ return dispatchEngine(context, command, parsed, classification, prepared);
508
1500
  }
509
1501
  if (family === "status") {
510
- const rootParsed = parseArgs([command ?? "", ...rest]);
511
1502
  return getRuntimeStatus(context, {
512
- projectRoot: optionalOption(rootParsed, "project-root") ?? optionalOption(rootParsed, "root"),
513
- checkRegistry: hasOption(rootParsed, "check-registry")
1503
+ projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root"),
1504
+ checkRegistry: hasOption(parsed, "check-registry")
514
1505
  });
515
1506
  }
516
1507
  if (family === "version") {
517
1508
  return getCliVersionReport();
518
1509
  }
519
1510
  if (family === "runtime") {
520
- return await dispatchRuntime(context, command, parsed);
1511
+ return await dispatchRuntime(context, command, parsed, commandProgress(io, output), prepared);
521
1512
  }
522
1513
  if (family === "canon") {
523
- return dispatchCanon(context, command, parsed);
1514
+ return dispatchCanon(context, command, parsed, prepared);
524
1515
  }
525
1516
  if (family === "project" && command === "register") {
526
1517
  return registerProject(context, { root: requiredOption(parsed, "root") });
@@ -597,16 +1588,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
597
1588
  return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
598
1589
  }
599
1590
  if (family === "protocol" && command === "transition") {
600
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
601
- const reason = optionalOption(parsed, "reason");
602
- return transitionProtocol(context, {
603
- protocolId,
604
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"),
605
- to: requiredOption(parsed, "to"),
606
- jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
607
- force: hasOption(parsed, "force"),
608
- ...(reason ? { reason } : {})
609
- });
1591
+ return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
610
1592
  }
611
1593
  if (family === "protocol" && command === "sync-from-run") {
612
1594
  const protocolId = requiredPosition(parsed, 0, "protocol-id");
@@ -631,17 +1613,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
631
1613
  });
632
1614
  }
633
1615
  if (family === "transition") {
634
- const transitionParsed = parseArgs([command ?? "", ...rest]);
635
- const protocolId = requiredPosition(transitionParsed, 0, "protocol-id");
636
- const reason = optionalOption(transitionParsed, "reason");
637
- return transitionProtocol(context, {
638
- protocolId,
639
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "transition"),
640
- to: requiredOption(transitionParsed, "to"),
641
- jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
642
- force: hasOption(transitionParsed, "force"),
643
- ...(reason ? { reason } : {})
644
- });
1616
+ return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
645
1617
  }
646
1618
  if (family === "plan" && command === "status") {
647
1619
  const protocolId = requiredPosition(parsed, 0, "protocol-id");
@@ -655,17 +1627,25 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
655
1627
  }
656
1628
  if (family === "stage" && command === "fanout") {
657
1629
  const action = requiredPosition(parsed, 0, "fanout action");
658
- const runId = requiredPosition(parsed, 1, "run-id");
659
1630
  const stage = requiredOption(parsed, "stage");
660
1631
  const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "stage fanout");
1632
+ const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
1633
+ const runId = resolveRun(context, project.id, requiredPosition(parsed, 1, "run-id")).id;
661
1634
  if (action === "status")
662
1635
  return getVnextFanoutStatus(context, { projectRoot, runId, stage });
1636
+ if (action === "reconcile")
1637
+ return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
663
1638
  if (action === "dispatch") {
664
1639
  if (stage === "plan-review")
665
1640
  return dispatchVnextPlanReview(context, { projectRoot, runId });
666
1641
  return getVnextFanoutStatus(context, { projectRoot, runId, stage });
667
1642
  }
668
- throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch> <RUN> --stage <stage> --project-root <path>", 2);
1643
+ throw new AppError("usage", "Use: dd-flow stage fanout <status|dispatch|reconcile> <RUN> --stage <stage> --project-root <path>; reconcile requires --observations-file <native-observations.json>", 2);
1644
+ }
1645
+ if (family === "stage" && command === "native") {
1646
+ if (requiredPosition(parsed, 0, "native action") !== "observe")
1647
+ throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
1648
+ return observeVnextNativeChildren(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "stage native observe"), runId: requiredPosition(parsed, 1, "run-id"), stage: requiredOption(parsed, "stage"), observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
669
1649
  }
670
1650
  if (family === "run" && command === "capacity") {
671
1651
  if (requiredPosition(parsed, 0, "capacity action") !== "record")
@@ -677,16 +1657,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
677
1657
  return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
678
1658
  }
679
1659
  if (family === "prompt" && command === "render") {
680
- const workspaceRoot = optionalOption(parsed, "workspace-root");
681
- const taskFile = optionalOption(parsed, "task-file");
682
- return renderWorkerPrompt(context, {
683
- projectRoot: requiredOption(parsed, "project-root"),
684
- runId: requiredOption(parsed, "run"),
685
- stage: requiredOption(parsed, "stage"),
686
- profile: requiredOption(parsed, "profile"),
687
- ...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }),
688
- ...(workspaceRoot ? { workspaceRoot } : {})
689
- });
1660
+ return renderWorkerPrompt(context, prepared.workerPrompt?.input ?? workerPromptInput(parsed), prepared.workerPrompt?.value);
690
1661
  }
691
1662
  if (family === "work" && command === "status") {
692
1663
  return getVnextWorkStatus(context, {
@@ -695,7 +1666,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
695
1666
  });
696
1667
  }
697
1668
  if (family === "work" && command === "add-batch") {
698
- return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
1669
+ return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch,
1670
+ ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}),
1671
+ ...(optionalOption(parsed, "request-key") ? { requestKey: optionalOption(parsed, "request-key") } : {}) });
699
1672
  }
700
1673
  if (family === "work" && command === "ls") {
701
1674
  return listWorks(context, { ...(optionalOption(parsed, "run") ? { runId: optionalOption(parsed, "run") } : {}), ...(optionalOption(parsed, "parent") ? { parentWorkId: optionalOption(parsed, "parent") } : {}), ...(optionalOption(parsed, "status") ? { status: optionalOption(parsed, "status") } : {}), ...(hasOption(parsed, "ready") ? { ready: true } : {}), ...(hasOption(parsed, "include-results") ? { includeResults: true } : {}), ...(optionalOption(parsed, "limit") ? { limit: Number(optionalOption(parsed, "limit")) } : {}) });
@@ -703,48 +1676,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
703
1676
  if (family === "work" && command === "show")
704
1677
  return showWork(context, requiredPosition(parsed, 0, "work-id"));
705
1678
  if (family === "work" && command === "repair" && requiredPosition(parsed, 0, "action") === "add") {
706
- if (!hasOption(parsed, "task-stdin"))
707
- throw new AppError("usage", "work repair add requires --task-stdin", 2);
708
- const origins = parsed.options.get("origin-work") ?? [];
709
- const findings = parsed.options.get("from-finding") ?? [];
710
- const unresolved = parsed.options.get("from-unresolved") ?? [];
711
- if (!origins.length && !findings.length)
712
- throw new AppError("usage", "work repair add requires --from-check with --origin-work, --from-finding, or --from-unresolved with --origin-work", 2);
713
- if (findings.length)
714
- return addVnextCodeReviewRepair(context, {
715
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
716
- runId: requiredOption(parsed, "run"),
717
- findingIds: findings,
718
- checkRefsByFinding: Object.fromEntries(findings.map((finding) => [finding, parsed.options.get("check-ref") ?? []])),
719
- objective: await readStdin(io.stdin)
720
- });
721
- if (unresolved.length) {
722
- if (!origins.length)
723
- throw new AppError("usage", "semantic repair requires at least one --origin-work", 2);
724
- const verificationPath = requiredOption(parsed, "verification-file");
725
- return addVnextCodeRepair(context, {
726
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
727
- runId: requiredOption(parsed, "run"),
728
- originWorkIds: origins,
729
- semanticUnresolved: unresolved,
730
- verificationPath,
731
- objective: await readStdin(io.stdin)
732
- });
733
- }
734
- if (!hasOption(parsed, "from-check"))
735
- throw new AppError("usage", "check repair requires --from-check", 2);
736
- if (!origins.length)
737
- throw new AppError("usage", "check repair requires at least one --origin-work", 2);
738
- const receipts = parsed.options.get("from-check") ?? [];
739
- if (receipts.length !== 1)
740
- throw new AppError("usage", "check repair accepts exactly one --from-check; create one causal repair Work per failed receipt", 2);
741
- return addVnextCodeRepair(context, {
742
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
743
- runId: requiredOption(parsed, "run"),
744
- checkReceiptId: receipts[0],
745
- originWorkIds: origins,
746
- objective: await readStdin(io.stdin)
747
- });
1679
+ if (!prepared.workRepair)
1680
+ throw new AppError("cli_input_unprepared", "Repair input was not prepared", 1);
1681
+ return addVnextCodeRepair(context, prepared.workRepair.input, prepared.workRepair.value);
748
1682
  }
749
1683
  if (family === "work" && command === "deps") {
750
1684
  const on = parsed.options.get("on") ?? [];
@@ -752,26 +1686,42 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
752
1686
  }
753
1687
  if (family === "work" && command === "delete")
754
1688
  return deleteWork(context, requiredPosition(parsed, 0, "work-id"));
1689
+ if (family === "work" && command === "launch")
1690
+ return await launchExternalWork(context, { workId: requiredPosition(parsed, 0, "work-id"), ...(optionalOption(parsed, "stage") ? { stage: optionalOption(parsed, "stage") } : {}), progress: commandProgress(io, output) });
755
1691
  if (family === "work" && command === "start") {
756
1692
  const hookEventId = optionalOption(parsed, "hook-event-id");
1693
+ const recoveryId = optionalOption(parsed, "recovery-id");
757
1694
  const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work start");
758
- return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), projectRoot });
1695
+ return startWork(context, requiredPosition(parsed, 0, "work-id"), { ...(hookEventId ? { hookEventId } : {}), ...(recoveryId ? { recoveryId } : {}), projectRoot });
759
1696
  }
760
1697
  if (family === "work" && command === "finish") {
761
1698
  const resultFile = optionalOption(parsed, "result-file");
762
1699
  const resultStdin = hasOption(parsed, "result-stdin");
763
1700
  if (Boolean(resultFile) === resultStdin)
764
1701
  throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
765
- const result = resultFile ? fs.readFileSync(path.resolve(resultFile), "utf8") : await readStdin(io.stdin);
766
- return await finishWork(context, requiredPosition(parsed, 0, "work-id"), result, commandProgress(io, output));
1702
+ const workId = requiredPosition(parsed, 0, "work-id");
1703
+ const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work finish");
1704
+ assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_finish", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
1705
+ const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = prepared.retryReason;
1706
+ if (Boolean(retryReceiptId) !== Boolean(retryReason))
1707
+ throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
1708
+ const maxBytes = workResultByteLimit(context, workId);
1709
+ const result = prepared.workFinish?.result ?? (resultFile ? prepareTextFile(resultFile, "Work result", maxBytes).text : await readStdin(io.stdin, maxBytes));
1710
+ return await finishWork(context, workId, result, commandProgress(io, output), retryReceiptId ? { receiptId: retryReceiptId, reason: retryReason } : undefined);
1711
+ }
1712
+ if (family === "work" && command === "fail") {
1713
+ const workId = requiredPosition(parsed, 0, "work-id");
1714
+ const projectRoot = requiredScopeProjectRoot(scopeProjectRoot, "work fail");
1715
+ assertWorkLifecycleOwner(context, { workId, projectRoot, operation: "work_fail", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
1716
+ return failWork(context, workId, requiredOption(parsed, "reason"));
767
1717
  }
768
- if (family === "work" && command === "fail")
769
- return failWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
770
1718
  if (family === "work" && command === "cancel")
771
1719
  return cancelWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
772
1720
  if (family === "work" && command === "retry")
773
1721
  return retryWork(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
774
1722
  if (family === "stage" && command === "start") {
1723
+ if (prepared.stageBindingBundle)
1724
+ context = { ...context, stageBindingBundle: prepared.stageBindingBundle };
775
1725
  const dir = optionalOption(parsed, "dir");
776
1726
  const bootstrap = hasOption(parsed, "bootstrap");
777
1727
  const subject = optionalOption(parsed, "subject");
@@ -788,12 +1738,40 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
788
1738
  const requireSessionBinding = hasOption(parsed, "require-session-binding");
789
1739
  const projectRoot = requiredOption(parsed, "project-root");
790
1740
  const stage = requiredOption(parsed, "stage");
791
- const externalContext = contextFile && contextSha256 ? loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) : undefined;
1741
+ const externalContext = prepared.stageStart?.externalContext;
1742
+ if (prepared.memoryStage?.operation === "start") {
1743
+ const runId = requiredPosition(parsed, 0, "run-id");
1744
+ const event = hookEventId ?? findRecentMatchingHookEvent(context, {
1745
+ projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
1746
+ matchKey: entityReferenceVariants(runId).map(reference => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
1747
+ errorCode: "trusted_session_binding_required", operation: "Memory stage start"
1748
+ }).eventKey;
1749
+ const work = startMemoryStage(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes });
1750
+ try {
1751
+ const binding = bindStageCoordinatorWork(context, { workId: work.work_id, hookEventId: event, stage,
1752
+ promptPath: work.prompt_path, resultPath: work.result_path, ...(contextSha256 ? { contextSha256 } : {}) });
1753
+ // The frozen semantic prompt is also restored by auxiliary owners in
1754
+ // other CLI contexts. Keep native invocation authority in this current
1755
+ // delivery, not in those immutable instruction/custody bytes.
1756
+ const finish = managedLifecycleCommand(context, runtimeCommand(context, ["stage", "finish", runId,
1757
+ "--stage", stage, "--project-root", projectRoot, "--result-file", work.result_path, "--json"]));
1758
+ const pause = stagePauseCommand(context, { projectRoot, runId, stage, workId: work.work_id });
1759
+ const orchestration = memoryStageFanoutDescriptor(context, { projectRoot, runId, stage });
1760
+ const delivery = { ...work, binding, ...(orchestration ? { orchestration } : {}), next: { finish_command: finish, pause_command: pause },
1761
+ worker_prompt_markdown: `${work.worker_prompt_markdown}\n## Current lifecycle commands\n\nWrite the semantic result to the assigned result path, then execute exactly:\n${finish}\n\nFor a required user choice, write the question file named by this command, then execute exactly:\n${pause}\n` };
1762
+ bindLifecyclePublishedPacket(context, delivery.worker_prompt_markdown);
1763
+ return controlLifecycleReply(context, { projectRoot, runId }, delivery);
1764
+ }
1765
+ catch (error) {
1766
+ throw new AppError("memory_stage_binding_pending", "Memory Stage allocated; trusted coordinator binding remains incomplete", 1, { effect: "committed", run_id: runId, stage, work_id: work.work_id, next_action: "reconcile_memory_stage_binding",
1767
+ cause: error instanceof AppError ? error.code : "coordinator_binding_failure" });
1768
+ }
1769
+ }
792
1770
  const claimVnextStageStart = (runId) => {
793
1771
  registerProject(context, { root: projectRoot });
794
1772
  return findRecentMatchingHookEvent(context, {
795
1773
  projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
796
- matchKey: stageStartMatchKey(runId, stage, projectRoot, contextSha256),
1774
+ matchKey: entityReferenceVariants(runId).map((reference) => stageStartMatchKey(reference, stage, projectRoot, contextSha256)),
797
1775
  errorCode: "trusted_session_binding_required",
798
1776
  operation: `vNext ${stage.toUpperCase()} start`
799
1777
  }).eventKey;
@@ -817,39 +1795,39 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
817
1795
  })
818
1796
  : claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
819
1797
  }
820
- const result = launchVnextSpecify(context, {
1798
+ const result = sdlcStageHandlers.specify.entry(context, {
821
1799
  projectRoot,
822
- ...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
1800
+ ...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
823
1801
  hookEventId,
824
1802
  ...(subject ? { slug: subject } : {}),
825
1803
  ...(externalContext ? { externalContext } : {}),
826
1804
  ...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
827
- });
1805
+ }, prepared.specifyStart);
828
1806
  return attachExternalStageContext(result, externalContext);
829
1807
  }
830
1808
  if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
831
1809
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
832
- return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1810
+ return attachExternalStageContext(await sdlcStageHandlers.protocolize.entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.protocolizeStart), externalContext);
833
1811
  }
834
1812
  if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
835
1813
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
836
- return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1814
+ return attachExternalStageContext(sdlcStageHandlers.plan.entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planStart), externalContext);
837
1815
  }
838
1816
  if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
839
1817
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
840
- return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1818
+ return attachExternalStageContext(sdlcStageHandlers["plan-review"].entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planReviewStart), externalContext);
841
1819
  }
842
1820
  if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
843
1821
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
844
- return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1822
+ return attachExternalStageContext(await sdlcStageHandlers.code.entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeStart), externalContext);
845
1823
  }
846
1824
  if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
847
1825
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
848
- return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1826
+ return attachExternalStageContext(sdlcStageHandlers["code-review"].entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeReviewStart), externalContext);
849
1827
  }
850
1828
  if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
851
1829
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
852
- return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1830
+ return attachExternalStageContext(await sdlcStageHandlers.merge.entry(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.mergeStart), externalContext);
853
1831
  }
854
1832
  if (intakeStdin) {
855
1833
  throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
@@ -864,7 +1842,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
864
1842
  ...(hookEventId ? { hookEventId } : {}),
865
1843
  ...(requireSessionBinding ? { requireSessionBinding: true } : {}),
866
1844
  ...(dir ? { dir } : {})
867
- });
1845
+ }, prepared.legacyStageStart);
868
1846
  }
869
1847
  if (family === "stage" && command === "finish") {
870
1848
  const dir = optionalOption(parsed, "dir");
@@ -874,9 +1852,34 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
874
1852
  const projectRoot = requiredOption(parsed, "project-root");
875
1853
  const runId = requiredPosition(parsed, 0, "run-id");
876
1854
  const stage = requiredOption(parsed, "stage");
1855
+ const controlledReply = (result) => controlLifecycleReply(context, { projectRoot, runId }, result);
877
1856
  const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
878
1857
  const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
879
1858
  const resultStdin = hasOption(parsed, "result-stdin");
1859
+ const retryCheckId = optionalOption(parsed, "retry-check");
1860
+ const retryReason = prepared.retryReason;
1861
+ const hookEventId = optionalOption(parsed, "hook-event-id");
1862
+ if (retryCheckId && !retryReason?.trim())
1863
+ throw new AppError("usage", "--retry-check requires --reason explaining the recovered environment", 2);
1864
+ if (prepared.memoryStage?.operation === "finish") {
1865
+ // Descriptor settlement validates exact coordinator custody and claims
1866
+ // the fresh hook atomically with result acceptance. Do not consume it
1867
+ // through the SDLC lifecycle owner before that settlement.
1868
+ return controlledReply(finishMemoryStage(context, { projectRoot, runId, stage, bytes: prepared.memoryStage.bytes,
1869
+ hookEventId: hookEventId ?? findRecentMatchingHookEvent(context, {
1870
+ projectId: requireProjectByRoot(context, resolveProjectRoot(projectRoot)).id,
1871
+ matchKey: entityReferenceVariants(runId).map(reference => stageLifecycleMatchKey("stage_finish", reference, stage, projectRoot)),
1872
+ errorCode: "trusted_session_binding_required", operation: "Memory stage finish"
1873
+ }).eventKey }));
1874
+ }
1875
+ const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
1876
+ || (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
1877
+ || (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
1878
+ || (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
1879
+ || (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
1880
+ || (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
1881
+ if (protectedLifecycle)
1882
+ assertStageLifecycleOwner(context, { projectRoot, runId, stage, operation: "stage_finish", ...(hookEventId ? { hookEventId } : {}) });
880
1883
  if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
881
1884
  const outcome = optionalOption(parsed, "outcome");
882
1885
  if (!outcome || !["specified", "failed", "cancelled"].includes(outcome)) {
@@ -888,84 +1891,92 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
888
1891
  if (!resultFile && !resultStdin) {
889
1892
  throw new AppError("usage", "vNext SPECIFY finish requires --result-file or --result-stdin", 2);
890
1893
  }
891
- return finishVnextSpecifyStage(context, {
1894
+ return controlledReply(await sdlcStageHandlers.specify.finish(context, {
892
1895
  projectRoot,
893
1896
  runId,
894
- ...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
1897
+ ...(resultFile ? { resultFile } : prepared.stageFinish?.value ? {} : { resultJson: await readStdin(io.stdin) }),
1898
+ ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}),
895
1899
  outcome: outcome
896
- });
1900
+ }));
897
1901
  }
898
1902
  if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
899
1903
  if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
900
1904
  throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
901
1905
  }
902
- return finishVnextProtocolize(context, { projectRoot, runId, resultFile });
1906
+ return controlledReply(await sdlcStageHandlers.protocolize.finish(context, { projectRoot, runId, resultFile, ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}) }));
903
1907
  }
904
1908
  if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
905
1909
  if (optionalOption(parsed, "code-work-batch-file"))
906
1910
  throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
907
- return finishVnextPlan(context, { projectRoot, runId });
1911
+ return controlledReply(await sdlcStageHandlers.plan.finish(context, { projectRoot, runId, ...(prepared.planFinish ? { prepared: prepared.planFinish } : {}) }));
908
1912
  }
909
1913
  if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
910
1914
  if (resultStdin || hasOption(parsed, "outcome"))
911
1915
  throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
912
1916
  const decisionFile = optionalOption(parsed, "decision-file");
913
- return finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) });
1917
+ return controlledReply(await sdlcStageHandlers["plan-review"].finish(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}) }));
914
1918
  }
915
1919
  if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
916
1920
  if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
917
1921
  throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
918
1922
  }
919
- return await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), progress: commandProgress(io, output) });
1923
+ return controlledReply(await sdlcStageHandlers.code.finish(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), ...(prepared.stageFinish?.value ? { verification: prepared.stageFinish.value } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
920
1924
  }
921
1925
  if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
922
1926
  if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
923
1927
  throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
924
- return await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), progress: commandProgress(io, output) });
1928
+ return controlledReply(await sdlcStageHandlers["code-review"].finish(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
925
1929
  }
926
1930
  if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
927
1931
  if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
928
1932
  throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
929
- return await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), progress: commandProgress(io, output) });
1933
+ return controlledReply(await sdlcStageHandlers.merge.finish(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }, prepared.mergeFinish));
930
1934
  }
931
1935
  if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
932
1936
  throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
933
1937
  }
934
- return finishStage(context, {
1938
+ return controlledReply(await finishStage(context, {
935
1939
  projectRoot,
936
1940
  runId,
937
1941
  stage,
938
1942
  ...(dir ? { dir } : {}),
939
1943
  ...(semanticFile ? { semanticFile } : {})
940
- });
1944
+ }, prepared.legacyStageFinish));
941
1945
  }
942
1946
  if (family === "stage" && command === "pause") {
943
- if (!hasOption(parsed, "question-stdin"))
944
- throw new AppError("usage", "stage pause requires --question-stdin", 2);
1947
+ const projectRoot = requiredOption(parsed, "project-root");
1948
+ const runId = requiredPosition(parsed, 0, "run-id");
1949
+ const stage = requiredOption(parsed, "stage");
1950
+ const workId = requiredOption(parsed, "work");
1951
+ const protectedLifecycle = (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId }))
1952
+ || (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId }))
1953
+ || (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId }))
1954
+ || (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId }))
1955
+ || (stage === "code" && isVnextPlanRun(context, { projectRoot, runId }))
1956
+ || (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId }));
1957
+ if (protectedLifecycle)
1958
+ assertStageLifecycleOwner(context, { projectRoot, runId, stage, workId, operation: "stage_pause", ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
945
1959
  return pauseStageForUser(context, {
946
- projectRoot: requiredOption(parsed, "project-root"),
947
- runId: requiredPosition(parsed, 0, "run-id"),
948
- stage: requiredOption(parsed, "stage"),
949
- workId: requiredOption(parsed, "work"),
950
- question: await readStdin(io.stdin)
1960
+ projectRoot,
1961
+ runId,
1962
+ stage,
1963
+ workId,
1964
+ question: prepared.interactionText ?? await readStdin(io.stdin)
951
1965
  });
952
1966
  }
953
1967
  if (family === "stage" && command === "resume") {
954
- if (!hasOption(parsed, "answer-stdin"))
955
- throw new AppError("usage", "stage resume requires --answer-stdin", 2);
956
1968
  const hookEventId = optionalOption(parsed, "hook-event-id");
957
1969
  return resumeStageAfterUser(context, {
958
1970
  projectRoot: requiredOption(parsed, "project-root"),
959
1971
  runId: requiredPosition(parsed, 0, "run-id"),
960
1972
  stage: requiredOption(parsed, "stage"),
961
1973
  workId: requiredOption(parsed, "work"),
962
- answer: await readStdin(io.stdin),
1974
+ answer: prepared.interactionText ?? await readStdin(io.stdin),
1975
+ evidence: prepared.resumeEvidence,
963
1976
  ...(hookEventId ? { hookEventId } : {})
964
1977
  });
965
1978
  }
966
1979
  if (family === "stage" && command === "block") {
967
- if (!hasOption(parsed, "summary-stdin"))
968
- throw new AppError("usage", "stage block requires --summary-stdin", 2);
969
1980
  const kind = requiredOption(parsed, "kind");
970
1981
  if (kind !== "engine" && kind !== "harness" && kind !== "environment")
971
1982
  throw new AppError("usage", "--kind must be engine, harness or environment", 2);
@@ -976,7 +1987,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
976
1987
  workId: requiredOption(parsed, "work"),
977
1988
  kind,
978
1989
  code: requiredOption(parsed, "code"),
979
- summary: await readStdin(io.stdin),
1990
+ summary: prepared.interactionText ?? await readStdin(io.stdin),
980
1991
  retryable: hasOption(parsed, "retryable")
981
1992
  });
982
1993
  }
@@ -1001,13 +2012,55 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1001
2012
  return dispatchMergeWorker(context, command, parsed);
1002
2013
  }
1003
2014
  if (family === "cleanup") {
1004
- return dispatchCleanup(context, command, parsed);
2015
+ return dispatchCleanup(context, command, parsed, prepared.cleanup);
1005
2016
  }
1006
2017
  if (family === "session") {
1007
- return dispatchSession(context, command, parsed);
2018
+ return dispatchSession(context, command, parsed, prepared.sessionPayload);
1008
2019
  }
1009
2020
  if (family === "run") {
1010
- return dispatchRun(context, command, parsed);
2021
+ if (command === "control") {
2022
+ const action = requiredPosition(parsed, 0, "control action");
2023
+ const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
2024
+ if (action === "status")
2025
+ return runControlStatus(context, scope);
2026
+ if (action === "reconcile") {
2027
+ if ([...parsed.options.keys()].some(key => !["run", "project-root", "control-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2028
+ throw new AppError("usage", "Use run control reconcile --run ID --project-root PATH --control-id ID --request-id ID", 2);
2029
+ return await reconcileRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id"), requestId: requiredOption(parsed, "request-id") });
2030
+ }
2031
+ if (action === "resume") {
2032
+ if (["force", "grace-ms", "escalate"].some(option => hasOption(parsed, option)))
2033
+ throw new AppError("usage", "Resume does not accept stop or pause escalation options", 2);
2034
+ return await resumeRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), controlId: requiredOption(parsed, "from"), ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {}) });
2035
+ }
2036
+ if (action === "serve")
2037
+ return await serveRunControl(context, { ...scope, controlId: requiredOption(parsed, "control-id") });
2038
+ if (action === "pause" || action === "stop")
2039
+ return await requestRunControl(context, {
2040
+ ...scope, action, requestId: requiredOption(parsed, "request-id"),
2041
+ ...(hasOption(parsed, "force") ? { force: true } : {}),
2042
+ ...(hasOption(parsed, "grace-ms") ? { graceMs: Number(requiredOption(parsed, "grace-ms")) } : {}),
2043
+ ...(hasOption(parsed, "escalate") ? { escalate: requiredOption(parsed, "escalate") } : {}),
2044
+ ...(hasOption(parsed, "wait-ms") ? { waitMs: Number(requiredOption(parsed, "wait-ms")) } : {})
2045
+ });
2046
+ throw new AppError("usage", "Use: dd-flow run control pause|stop|resume|reconcile|status", 2);
2047
+ }
2048
+ if (command === "drive") {
2049
+ const action = requiredPosition(parsed, 0, "drive action");
2050
+ const scope = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run") };
2051
+ if (action === "status")
2052
+ return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
2053
+ if (action === "launch")
2054
+ return await launchRunController(context, controllerLaunchInput(parsed), prepared.controllerLaunch);
2055
+ if (action === "serve")
2056
+ return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
2057
+ if (action === "answer")
2058
+ return answerRunController(context, controllerAnswerInput(parsed), prepared.controllerAnswer);
2059
+ if (action === "context")
2060
+ return supplyRunControllerContext(context, controllerContextInput(parsed), prepared.controllerContext);
2061
+ throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
2062
+ }
2063
+ return dispatchRun(context, command, parsed, prepared);
1011
2064
  }
1012
2065
  if (family === "stat") {
1013
2066
  return dispatchStat(context, command, parsed);
@@ -1022,22 +2075,26 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1022
2075
  return dispatchDashboard(context, command, parsed);
1023
2076
  }
1024
2077
  if (family === "schema") {
1025
- return dispatchSchema(context, command, parsed);
2078
+ return prepared.schemaValidation;
1026
2079
  }
1027
2080
  if (family === "memory") {
1028
- return dispatchMemory(command, parsed);
2081
+ return dispatchMemory(context, command, parsed, prepared);
1029
2082
  }
1030
2083
  if (family === "migration") {
1031
- return dispatchMigration(context, command, parsed, classification);
2084
+ return dispatchMigration(context, command, parsed, classification, prepared);
1032
2085
  }
1033
2086
  if (family === "codex" && command === "hooks") {
1034
- return dispatchCodexHooks(context, parsed);
2087
+ return dispatchCodexHooks(context, parsed, prepared);
1035
2088
  }
1036
2089
  if (family === "codex" && command === "home") {
1037
- return dispatchCodexHome(context, parsed);
2090
+ return dispatchCodexHome(context, parsed, prepared);
1038
2091
  }
1039
2092
  if (family === "codex" && command === "hook") {
1040
- return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
2093
+ // `handle` is owned by the pre-context ingress above, never generic dispatch.
2094
+ throw new AppError("usage", "Use: dd-flow codex hook handle --event EVENT", 2);
2095
+ }
2096
+ if (family === "zcode" && command === "hook") {
2097
+ return dispatchZcodeHook(context, parsed, await readStdin(io.stdin));
1041
2098
  }
1042
2099
  if (family === "zcode" && command === "event") {
1043
2100
  return dispatchZcodeEvent(context, parsed, await readStdin(io.stdin));
@@ -1063,6 +2120,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1063
2120
  throw new AppError("usage", "Unknown opencode usage action", 2);
1064
2121
  return ingestOpenCodeUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1065
2122
  }
2123
+ if (family === "droid" && command === "event") {
2124
+ if (requiredPosition(parsed, 0, "droid event action") !== "handle")
2125
+ throw new AppError("usage", "Unknown droid event action", 2);
2126
+ return handleDroidEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
2127
+ }
2128
+ if (family === "droid" && command === "usage") {
2129
+ if (requiredPosition(parsed, 0, "droid usage action") !== "ingest")
2130
+ throw new AppError("usage", "Unknown droid usage action", 2);
2131
+ return ingestDroidUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
2132
+ }
1066
2133
  if (family === "agy" && command === "event")
1067
2134
  return dispatchAgyEvent(context, parsed, await readStdin(io.stdin));
1068
2135
  if (family === "agy" && command === "usage") {
@@ -1071,11 +2138,14 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1071
2138
  return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1072
2139
  }
1073
2140
  if (family === "worktree") {
1074
- return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
2141
+ return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`), prepared);
1075
2142
  }
1076
2143
  throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
1077
2144
  }
1078
- function dispatchEngine(context, command, parsed, classification) {
2145
+ function engineBindInput(parsed) {
2146
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), packageName: optionalOption(parsed, "package"), engineVersion: requiredOption(parsed, "engine-version"), integrityChecksum: optionalOption(parsed, "integrity-checksum"), reason: requiredOption(parsed, "reason") };
2147
+ }
2148
+ function dispatchEngine(context, command, parsed, classification, prepared) {
1079
2149
  if (command === "install") {
1080
2150
  return installCurrentEngine(context, { force: hasOption(parsed, "force") });
1081
2151
  }
@@ -1100,18 +2170,11 @@ function dispatchEngine(context, command, parsed, classification) {
1100
2170
  });
1101
2171
  }
1102
2172
  if (command === "bind-run") {
1103
- return bindRunEngine(context, {
1104
- projectRoot: requiredOption(parsed, "project-root"),
1105
- runId: requiredPosition(parsed, 0, "run-id"),
1106
- packageName: optionalOption(parsed, "package"),
1107
- engineVersion: requiredOption(parsed, "engine-version"),
1108
- integrityChecksum: optionalOption(parsed, "integrity-checksum"),
1109
- reason: requiredOption(parsed, "reason")
1110
- });
2173
+ return bindRunEngine(context, engineBindInput(parsed), prepared.engineBind);
1111
2174
  }
1112
2175
  throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
1113
2176
  }
1114
- function dispatchMigration(context, command, parsed, classification) {
2177
+ function dispatchMigration(context, command, parsed, classification, prepared) {
1115
2178
  if (command === "impact")
1116
2179
  return assessMigrationImpact(context, { projectRoot: requiredOption(parsed, "project-root"), ...(optionalOption(parsed, "target-version") ? { targetVersion: optionalOption(parsed, "target-version") } : {}), ...(optionalOption(parsed, "mode") ? { mode: optionalOption(parsed, "mode") } : {}) });
1117
2180
  if (command === "plan" || command === "report") {
@@ -1127,26 +2190,42 @@ function dispatchMigration(context, command, parsed, classification) {
1127
2190
  });
1128
2191
  }
1129
2192
  if (command === "verify") {
1130
- return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
2193
+ return prepared.migrationVerification ?? verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
1131
2194
  }
1132
2195
  if (command === "apply") {
1133
- return applyMigration(context, {
1134
- projectRoot: requiredOption(parsed, "project-root"),
1135
- sourceVersion: optionalOption(parsed, "source-version"),
1136
- targetVersion: optionalOption(parsed, "target-version"),
1137
- runId: optionalOption(parsed, "run"),
1138
- backupPath: optionalOption(parsed, "backup-path"),
1139
- backupCreatedAt: optionalOption(parsed, "backup-created-at"),
1140
- reportFile: optionalOption(parsed, "file"),
1141
- allowActive: false,
1142
- classification
1143
- });
2196
+ return applyMigration(context, migrationApplyInput(parsed, classification), prepared.migrationApply);
1144
2197
  }
1145
2198
  throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
1146
2199
  }
1147
- function dispatchCanon(context, command, parsed) {
2200
+ function protocolTransitionInput(parsed, scopeProjectRoot) {
2201
+ const reason = optionalOption(parsed, "reason");
2202
+ return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"), to: requiredOption(parsed, "to"), jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]), force: hasOption(parsed, "force"), ...(reason ? { reason } : {}) };
2203
+ }
2204
+ function controllerAnswerInput(parsed) {
2205
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), requestId: requiredOption(parsed, "request-id"), pauseId: requiredOption(parsed, "pause-id"), answerFile: requiredOption(parsed, "answer-file") };
2206
+ }
2207
+ function controllerLaunchInput(parsed) {
2208
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), requestId: requiredOption(parsed, "request-id"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256"), ...(optionalOption(parsed, "stop-after") ? { stopAfter: optionalOption(parsed, "stop-after") } : {}), ...(optionalOption(parsed, "capture-root") ? { captureRoot: optionalOption(parsed, "capture-root") } : {}) };
2209
+ }
2210
+ function controllerContextInput(parsed) {
2211
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), controllerId: requiredOption(parsed, "controller-id"), requestId: requiredOption(parsed, "request-id"), stage: requiredOption(parsed, "stage"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256") };
2212
+ }
2213
+ function migrationApplyInput(parsed, classification) {
2214
+ return {
2215
+ projectRoot: requiredOption(parsed, "project-root"),
2216
+ sourceVersion: optionalOption(parsed, "source-version"),
2217
+ targetVersion: optionalOption(parsed, "target-version"),
2218
+ runId: optionalOption(parsed, "run"),
2219
+ backupPath: optionalOption(parsed, "backup-path"),
2220
+ backupCreatedAt: optionalOption(parsed, "backup-created-at"),
2221
+ reportFile: optionalOption(parsed, "file"),
2222
+ allowActive: false,
2223
+ classification
2224
+ };
2225
+ }
2226
+ function dispatchCanon(context, command, parsed, prepared) {
1148
2227
  if (command === "register") {
1149
- return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
2228
+ return registerCanonRoot(context, { root: requiredOption(parsed, "root") }, prepared.canonRegistration);
1150
2229
  }
1151
2230
  if (command === "status") {
1152
2231
  return getCanonStatus(context, { root: optionalOption(parsed, "root") });
@@ -1156,6 +2235,15 @@ function dispatchCanon(context, command, parsed) {
1156
2235
  }
1157
2236
  throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
1158
2237
  }
2238
+ function mergeJobTarget(parsed, scopeProjectRoot) {
2239
+ return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed) };
2240
+ }
2241
+ function mergeCancellationInput(parsed, scopeProjectRoot) {
2242
+ return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"), reason: requiredOption(parsed, "reason"), workerId: optionalOption(parsed, "worker-id"), workspacePath: optionalOption(parsed, "path"), force: hasOption(parsed, "force") };
2243
+ }
2244
+ function mergeWaitInput(parsed, scopeProjectRoot) {
2245
+ return { projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"), workerId: requiredWorkerId(parsed), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval"), workspacePath: workspacePathOption(parsed), acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false };
2246
+ }
1159
2247
  function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1160
2248
  if (hasOption(parsed, "session-id")) {
1161
2249
  throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
@@ -1179,32 +2267,17 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1179
2267
  });
1180
2268
  }
1181
2269
  if (command === "wait-next") {
1182
- return waitNextMergeJob(context, {
1183
- projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
1184
- workerId: requiredWorkerId(parsed),
1185
- timeoutSeconds: optionalNumber(parsed, "timeout"),
1186
- pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
1187
- workspacePath: workspacePathOption(parsed),
1188
- acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false
1189
- });
2270
+ return waitNextMergeJob(context, mergeWaitInput(parsed, scopeProjectRoot));
1190
2271
  }
1191
2272
  if (command === "complete") {
1192
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1193
2273
  return completeMergeJob(context, {
1194
- protocolId,
1195
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
1196
- workerId: requiredWorkerId(parsed),
1197
- workspacePath: workspacePathOption(parsed),
2274
+ ...mergeJobTarget(parsed, scopeProjectRoot),
1198
2275
  summary: requiredOption(parsed, "summary")
1199
2276
  });
1200
2277
  }
1201
2278
  if (command === "fail") {
1202
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1203
2279
  return failMergeJob(context, {
1204
- protocolId,
1205
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
1206
- workerId: requiredWorkerId(parsed),
1207
- workspacePath: workspacePathOption(parsed),
2280
+ ...mergeJobTarget(parsed, scopeProjectRoot),
1208
2281
  reason: requiredOption(parsed, "reason"),
1209
2282
  requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
1210
2283
  });
@@ -1219,15 +2292,7 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1219
2292
  });
1220
2293
  }
1221
2294
  if (command === "cancel") {
1222
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1223
- return cancelMergeQueueJob(context, {
1224
- protocolId,
1225
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"),
1226
- reason: requiredOption(parsed, "reason"),
1227
- workerId: optionalOption(parsed, "worker-id"),
1228
- workspacePath: optionalOption(parsed, "path"),
1229
- force: hasOption(parsed, "force")
1230
- });
2295
+ return cancelMergeQueueJob(context, mergeCancellationInput(parsed, scopeProjectRoot));
1231
2296
  }
1232
2297
  throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
1233
2298
  }
@@ -1326,7 +2391,7 @@ function dispatchMergeWorker(context, command, parsed) {
1326
2391
  }
1327
2392
  throw new AppError("usage", `Unknown merge-worker command: ${command ?? "<empty>"}`, 2);
1328
2393
  }
1329
- function dispatchMemory(command, parsed) {
2394
+ function dispatchMemory(context, command, parsed, prepared) {
1330
2395
  const subcommand = requiredPosition(parsed, 0, "memory subcommand");
1331
2396
  if (command === "permissions" && subcommand === "preflight") {
1332
2397
  return preflightMemoryPermissions({
@@ -1337,6 +2402,103 @@ function dispatchMemory(command, parsed) {
1337
2402
  mode: requiredOption(parsed, "mode")
1338
2403
  });
1339
2404
  }
2405
+ const scope = () => {
2406
+ const projectRoot = requiredOption(parsed, "project-root");
2407
+ const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
2408
+ return { projectRoot, runId: resolveRun(context, project.id, requiredOption(parsed, "run")).id };
2409
+ };
2410
+ if (command === "repair") {
2411
+ const wave = requiredOption(parsed, "wave");
2412
+ if (wave !== "1" && wave !== "2")
2413
+ throw new AppError("usage", "Memory repairs support only waves 1 and 2", 2);
2414
+ const work = { ...scope(), workId: requiredOption(parsed, "work") };
2415
+ if (subcommand === "baseline")
2416
+ return (wave === "1" ? registerMemoryRepairBaselineFromStage : registerMemorySecondRepairBaselineFromStage)(context, work);
2417
+ if (subcommand === "copy-reserve")
2418
+ return (wave === "1" ? reserveMemoryRepairWriterCopyFromStage : reserveMemorySecondRepairCopyFromStage)(context, work);
2419
+ if (subcommand === "copy-materialize")
2420
+ return (wave === "1" ? materializeMemoryRepairWriterCopyFromStage : materializeMemorySecondRepairCopyFromStage)(context, work);
2421
+ if (subcommand === "copy-dispatch")
2422
+ return (wave === "1" ? prepareMemoryRepairDispatchFromStage : prepareMemorySecondRepairDispatchFromStage)(context, work);
2423
+ const workspaceRoot = requiredOption(parsed, "workspace-root");
2424
+ if (wave === "2") {
2425
+ const bundle = readFlowRunExecutionBundle(context, work);
2426
+ if (!bundle || bundle.descriptor.descriptor.id !== "mb-init" || bundle.execution.snapshot().scope.workspace !== workspaceRoot)
2427
+ throw new AppError("memory_repair_scope_invalid", "Repair workspace does not match its frozen memory RUN", 2, { phase: "prepare", effect: "no_effect" });
2428
+ }
2429
+ if (subcommand === "current-view")
2430
+ return wave === "1"
2431
+ ? registerMemoryRepairCurrentViewFromStage(context, { ...work, workspaceRoot })
2432
+ : registerMemorySecondRepairCurrentViewFromStage(context, work);
2433
+ const output = { ...work, workspaceRoot, workSessionId: requiredOption(parsed, "work-session") };
2434
+ if (subcommand === "publication-source" && wave === "1")
2435
+ return registerMemoryRepairPublicationSourceFromStage(context, output);
2436
+ if (subcommand === "publication-reserve") {
2437
+ if (!prepared.memoryOperationBytes)
2438
+ throw new AppError("cli_input_unprepared", "Memory repair output bytes were not prepared", 1);
2439
+ return (wave === "1" ? reserveMemoryRepairPublicationAgainstSourceFromStage : reserveMemorySecondRepairPublicationFromStage)(context, { ...output, bytes: prepared.memoryOperationBytes });
2440
+ }
2441
+ if (subcommand === "publication-apply")
2442
+ return (wave === "1" ? applyMemoryRepairPublicationFromStage : applyMemorySecondRepairPublicationFromStage)(context, output);
2443
+ if (subcommand === "publication-finish")
2444
+ return (wave === "1" ? finishMemoryRepairPublicationFromStage : finishMemorySecondRepairPublicationFromStage)(context, output);
2445
+ }
2446
+ if (command === "compose" && subcommand === "register") {
2447
+ if (!prepared.memoryOperationBytes)
2448
+ throw new AppError("cli_input_unprepared", "Memory composition bytes were not prepared", 1);
2449
+ try {
2450
+ return registerMemoryCompositionFromStage(context, { ...scope(), requestKey: requiredOption(parsed, "request-key"), bytes: prepared.memoryOperationBytes });
2451
+ }
2452
+ catch (error) {
2453
+ if (error instanceof AppError && (error.code === "memory_stage_result_invalid"
2454
+ || error.code === "memory_preflight_handler_invalid" && ["unselected_preflight_source", "proposed_source_sha256_mismatch"].includes(String(error.details.reason))) && error.details.phase === "prepare"
2455
+ && error.details.effect === "no_effect" && error.details.recoverable === true) {
2456
+ error.details.retry_command = runtimeCommand(context, ["memory", "compose", "register", "--run", requiredOption(parsed, "run"),
2457
+ "--project-root", requiredOption(parsed, "project-root"), "--request-key", requiredOption(parsed, "request-key"),
2458
+ "--result-file", requiredOption(parsed, "result-file"), "--json", "--progress-jsonl"]);
2459
+ error.details.retry_instruction = "Correct only the rejected composition proposal, then execute retry_command in this same Session. No Work graph was registered by this rejected call; do not dispatch children or finish the Stage until registration succeeds.";
2460
+ }
2461
+ throw error;
2462
+ }
2463
+ }
2464
+ if (command === "verify" && subcommand === "checks")
2465
+ return runMemoryVerificationChecks(context, scope());
2466
+ if (command === "review" && subcommand === "register")
2467
+ return registerMemoryReviewFromStage(context, scope());
2468
+ if (command === "review" && subcommand === "collect") {
2469
+ const wave = collectMemoryInitialReviewFromStage(context, scope());
2470
+ const selected = scope(), bundle = readFlowRunExecutionBundle(context, selected);
2471
+ if (!bundle)
2472
+ throw new AppError("memory_review_handler_invalid", "Registered Memory bundle required", 2);
2473
+ const project = requireProjectByRoot(context, resolveProjectRoot(selected.projectRoot));
2474
+ const retained = PreparedRetainedMemoryReviewResults.prepare(context, PreparedRetainedMemoryReviewAuthority.prepare(context, { projectId: project.id, runId: selected.runId }, bundle));
2475
+ return { references: wave.references(), projection: wave.resultProjection(),
2476
+ correction_plan_binding: { schema_id: "dd-flow/memory-review-correction-plan@1", run_id: selected.runId, wave: 1, review_sha256: retained.sha256 } };
2477
+ }
2478
+ if (command === "finish" && subcommand === "evidence")
2479
+ return registerMemoryFinishEvidenceFromStage(context, scope());
2480
+ if (command === "finish" && subcommand === "integrate")
2481
+ return integrateMemoryFinishFromStage(context, scope());
2482
+ if (command === "corrections" || command === "review" && ["selective-register", "selective-collect"].includes(subcommand)) {
2483
+ const wave = requiredOption(parsed, "wave");
2484
+ if (wave !== "1" && wave !== "2")
2485
+ throw new AppError("usage", "Memory corrections support only waves 1 and 2", 2);
2486
+ if (command === "corrections" && subcommand === "register") {
2487
+ if (!prepared.memoryOperationBytes)
2488
+ throw new AppError("cli_input_unprepared", "Memory correction plan bytes were not prepared", 1);
2489
+ const register = wave === "1" ? registerMemoryCorrectionsFromStage : registerMemorySecondCorrectionsFromStage;
2490
+ return register(context, { ...scope(), bytes: prepared.memoryOperationBytes });
2491
+ }
2492
+ if (command === "corrections" && subcommand === "checks")
2493
+ return (wave === "1" ? runMemoryCorrectionChecksFromStage : runMemorySecondCorrectionChecksFromStage)(context, scope());
2494
+ if (command === "review" && subcommand === "selective-register")
2495
+ return (wave === "1" ? registerCorrectedMemoryReviewFromStage : registerSecondCorrectedMemoryReviewFromStage)(context, scope());
2496
+ if (command === "review" && subcommand === "selective-collect") {
2497
+ const accepted = (wave === "1" ? collectCorrectedMemoryReviewFromStage : collectSecondCorrectedMemoryReviewFromStage)(context, scope());
2498
+ return { wave: Number(wave), references: accepted.references(), projection: accepted.resultProjection(),
2499
+ ...(wave === "1" ? { correction_plan_binding: { schema_id: "dd-flow/memory-review-correction-plan@1", run_id: scope().runId, wave: 2, review_sha256: accepted.sha256 } } : {}) };
2500
+ }
2501
+ }
1340
2502
  throw new AppError("usage", `Unknown memory command: ${["memory", command, subcommand].filter(Boolean).join(" ")}`, 2);
1341
2503
  }
1342
2504
  function completedCommandExitCode(payload) {
@@ -1345,7 +2507,7 @@ function completedCommandExitCode(payload) {
1345
2507
  const exitCode = payload.exit_code;
1346
2508
  return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
1347
2509
  }
1348
- function dispatchCleanup(context, command, parsed) {
2510
+ function dispatchCleanup(context, command, parsed, prepared) {
1349
2511
  if (command === "scan") {
1350
2512
  return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
1351
2513
  }
@@ -1353,6 +2515,7 @@ function dispatchCleanup(context, command, parsed) {
1353
2515
  return cleanupApply(context, {
1354
2516
  projectRoot: requiredOption(parsed, "project-root"),
1355
2517
  planFile: requiredOption(parsed, "plan-file"),
2518
+ prepared,
1356
2519
  reason: requiredOption(parsed, "reason"),
1357
2520
  force: hasOption(parsed, "force")
1358
2521
  });
@@ -1373,12 +2536,14 @@ function dispatchProjectConfig(context, parsed) {
1373
2536
  }
1374
2537
  throw new AppError("usage", `Unknown project config action: ${action}`, 2);
1375
2538
  }
1376
- function dispatchSession(context, command, parsed) {
2539
+ function dispatchSession(context, command, parsed, preparedPayload) {
1377
2540
  if (command === "register") {
1378
2541
  return registerFlowSession(context, {
2542
+ ...(preparedPayload ? { preparedPayload } : {}),
1379
2543
  payloadBase64: optionalOption(parsed, "payload-base64"),
1380
2544
  payloadJson: optionalOption(parsed, "payload-json"),
1381
2545
  payloadFile: optionalOption(parsed, "payload-file"),
2546
+ hookEventId: optionalOption(parsed, "hook-event-id"),
1382
2547
  sessionId: optionalOption(parsed, "session-id")
1383
2548
  });
1384
2549
  }
@@ -1411,7 +2576,47 @@ function dispatchSession(context, command, parsed) {
1411
2576
  }
1412
2577
  throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
1413
2578
  }
1414
- function dispatchRun(context, command, parsed) {
2579
+ function snapshotCreateInput(parsed) {
2580
+ const recoveryId = optionalOption(parsed, "recovery-id"), stageEntry = optionalOption(parsed, "stage-entry");
2581
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), output: requiredOption(parsed, "output"), candidate: parsed.options.has("candidate"), incomplete: parsed.options.has("incomplete"), ...(recoveryId !== undefined ? { recoveryId } : {}), ...(stageEntry !== undefined ? { stageEntry } : {}) };
2582
+ }
2583
+ function snapshotRestoreInput(parsed) {
2584
+ return { snapshot: requiredOption(parsed, "snapshot"), projectRoot: requiredOption(parsed, "project-root"), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), operationalDecisionFile: optionalOption(parsed, "operational-decision-file"), ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}) };
2585
+ }
2586
+ function runStartInput(parsed) {
2587
+ const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
2588
+ return { projectRoot: requiredOption(parsed, "project-root"), workspaceRoot: optionalOption(parsed, "workspace-root"), flowKind: requiredOption(parsed, "flow-kind"), subjectType: requiredOption(parsed, "subject-type"), subjectId: requiredOption(parsed, "subject-id"), slug: requiredOption(parsed, "slug"), nextAction: optionalOption(parsed, "next-action"), preset: optionalOption(parsed, "preset"), taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), ...(protocolOverrides !== undefined ? { protocolOverrides } : {}), runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag")) };
2589
+ }
2590
+ function runForkInput(parsed) {
2591
+ return { snapshot: requiredOption(parsed, "from"), output: requiredOption(parsed, "output"), engineVersion: requiredOption(parsed, "engine-version"), requestId: requiredOption(parsed, "request-id"), ...(optionalOption(parsed, "integrity-checksum") ? { integrityChecksum: optionalOption(parsed, "integrity-checksum") } : {}), ...(optionalOption(parsed, "operational-decision-file") ? { operationalDecisionFile: optionalOption(parsed, "operational-decision-file") } : {}) };
2592
+ }
2593
+ function recoveryCaptureInput(parsed) {
2594
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") };
2595
+ }
2596
+ function recoveryResumeInput(parsed) {
2597
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), daemonId: requiredOption(parsed, "daemon-id"), harness: requiredOption(parsed, "harness"), nativeSessionId: requiredOption(parsed, "native-session-id") };
2598
+ }
2599
+ function dispatchRun(context, command, parsed, prepared = {}) {
2600
+ if (command === "reconcile")
2601
+ return reconcileFlowRunHealth(context, { runId: parsed.positional[0], projectRoot: optionalOption(parsed, "project-root"), apply: hasOption(parsed, "apply"), noProgressMs: optionalNumber(parsed, "no-progress-ms") });
2602
+ if (command === "recovery") {
2603
+ const action = requiredPosition(parsed, 0, "recovery action");
2604
+ const projectRoot = requiredOption(parsed, "project-root");
2605
+ const runId = requiredPosition(parsed, 1, "run-id");
2606
+ if (action === "inspect")
2607
+ return inspectRunRecovery(context, { projectRoot, runId });
2608
+ if (action === "begin")
2609
+ return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") }, prepared.recoveryPayload);
2610
+ if (action === "seal")
2611
+ return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") }, prepared.recoveryPayload);
2612
+ if (action === "capture")
2613
+ return recordRecoveryCapture(context, recoveryCaptureInput(parsed), prepared.recoveryCapture);
2614
+ if (action === "resume")
2615
+ return resumeRunRecovery(context, recoveryResumeInput(parsed), prepared.recoveryResume);
2616
+ if (action === "accept")
2617
+ return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
2618
+ throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
2619
+ }
1415
2620
  if (command === "snapshot") {
1416
2621
  const action = requiredPosition(parsed, 0, "snapshot action");
1417
2622
  if (action === "bootstrap") {
@@ -1419,59 +2624,31 @@ function dispatchRun(context, command, parsed) {
1419
2624
  if (bootstrapAction === "create")
1420
2625
  return createEvalBootstrapSnapshot(context, {
1421
2626
  projectRoot: requiredOption(parsed, "project-root"),
1422
- output: requiredOption(parsed, "output")
2627
+ output: requiredOption(parsed, "output"),
2628
+ flow: optionalOption(parsed, "flow")
1423
2629
  });
1424
2630
  if (bootstrapAction === "restore")
1425
- return restoreEvalBootstrapSnapshot(context, {
1426
- snapshot: requiredOption(parsed, "snapshot"),
1427
- projectRoot: requiredOption(parsed, "project-root")
1428
- });
2631
+ return restoreEvalBootstrapSnapshot(context, snapshotRestoreInput(parsed), prepared.bootstrapRestore);
1429
2632
  throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
1430
2633
  }
1431
2634
  if (action === "create") {
1432
- const candidate = parsed.options.has("candidate");
1433
- const stageEntry = optionalOption(parsed, "stage-entry");
1434
- if (candidate && stageEntry !== undefined)
1435
- throw new AppError("usage", "Use either --candidate or --stage-entry, not both", 2);
1436
- if (!candidate && stageEntry === undefined)
1437
- throw new AppError("usage", "Snapshot create requires --candidate or --stage-entry", 2);
1438
- const input = {
1439
- projectRoot: requiredOption(parsed, "project-root"),
1440
- runId: requiredPosition(parsed, 1, "run-id"),
1441
- output: requiredOption(parsed, "output")
1442
- };
1443
- return candidate
1444
- ? createEvalRunSnapshot(context, { ...input, candidate: true })
1445
- : createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
2635
+ return createEvalRunSnapshot(context, snapshotCreateInput(parsed));
1446
2636
  }
1447
2637
  if (action === "restore")
1448
- return restoreEvalRunSnapshot(context, {
1449
- snapshot: requiredOption(parsed, "snapshot"),
1450
- projectRoot: requiredOption(parsed, "project-root")
1451
- });
2638
+ return restoreEvalRunSnapshot(context, snapshotRestoreInput(parsed), prepared.snapshotRestore);
1452
2639
  throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore | dd-flow run snapshot create <RUN> (--stage-entry <stage>|--candidate) --project-root <path> --output <directory> | dd-flow run snapshot restore --snapshot <directory> --project-root <path>", 2);
1453
2640
  }
2641
+ if (command === "fork") {
2642
+ return forkRunFromSnapshot(context, runForkInput(parsed), prepared.runFork);
2643
+ }
1454
2644
  if (command === "prepare-vnext-specify") {
1455
- return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug") });
2645
+ return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
1456
2646
  }
1457
2647
  if (command === "start") {
1458
2648
  if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
1459
2649
  assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
1460
2650
  }
1461
- const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
1462
- return startFlowRun(context, {
1463
- projectRoot: requiredOption(parsed, "project-root"),
1464
- workspaceRoot: optionalOption(parsed, "workspace-root"),
1465
- flowKind: requiredOption(parsed, "flow-kind"),
1466
- subjectType: requiredOption(parsed, "subject-type"),
1467
- subjectId: requiredOption(parsed, "subject-id"),
1468
- slug: requiredOption(parsed, "slug"),
1469
- nextAction: optionalOption(parsed, "next-action"),
1470
- preset: optionalOption(parsed, "preset"),
1471
- taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
1472
- ...(protocolOverrides ? { protocolOverrides } : {}),
1473
- runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
1474
- });
2651
+ return startFlowRun(context, prepared.runStart?.input ?? runStartInput(parsed), prepared.runStart?.value);
1475
2652
  }
1476
2653
  if (command === "status") {
1477
2654
  return getFlowRunStatus(context, {
@@ -1541,84 +2718,80 @@ function dispatchRun(context, command, parsed) {
1541
2718
  throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
1542
2719
  }
1543
2720
  if (command === "attach-stage") {
1544
- return attachFlowRunStage(context, {
1545
- projectRoot: requiredOption(parsed, "project-root"),
1546
- runId: requiredPosition(parsed, 0, "run-id"),
1547
- stage: requiredOption(parsed, "stage"),
1548
- dir: requiredOption(parsed, "dir"),
1549
- status: requiredOption(parsed, "status"),
1550
- dataSchemaId: optionalOption(parsed, "data-schema-id")
1551
- });
2721
+ return attachFlowRunStage(context, stageAttachmentInput(parsed));
1552
2722
  }
1553
2723
  if (command === "complete-stage") {
1554
- const stageReport = optionalOption(parsed, "stage-report") ?? optionalOption(parsed, "dashboard");
1555
- return completeFlowRunStage(context, {
1556
- projectRoot: requiredOption(parsed, "project-root"),
1557
- runId: requiredPosition(parsed, 0, "run-id"),
1558
- stage: requiredOption(parsed, "stage"),
1559
- status: requiredOption(parsed, "status"),
1560
- stageReport,
1561
- data: optionalOption(parsed, "data"),
1562
- dataSchemaId: optionalOption(parsed, "data-schema-id"),
1563
- report: optionalOption(parsed, "report"),
1564
- aliases: allOptions(parsed, "alias")
1565
- });
2724
+ const input = stageCompletionInput(parsed);
2725
+ return completeFlowRunStage(context, input, prepareRawFlowRunStageCompletion(context, input));
1566
2726
  }
1567
2727
  if (command === "complete") {
1568
2728
  throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
1569
2729
  }
1570
2730
  if (command === "override") {
1571
- const status = requiredOption(parsed, "status");
1572
- if (!['cancelled', 'failed'].includes(status)) {
1573
- throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
1574
- }
1575
- return completeFlowRun(context, {
1576
- projectRoot: requiredOption(parsed, "project-root"),
1577
- runId: requiredPosition(parsed, 0, "run-id"),
1578
- status,
1579
- verdict: optionalOption(parsed, "verdict"),
1580
- nextAction: optionalOption(parsed, "next-action"),
1581
- manualOverrideReason: requiredOption(parsed, "reason")
1582
- });
2731
+ return completeFlowRun(context, runOverrideInput(parsed));
1583
2732
  }
1584
2733
  throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
1585
2734
  }
1586
- async function dispatchRuntime(context, command, parsed) {
2735
+ function stageAttachmentInput(parsed) {
2736
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), dir: requiredOption(parsed, "dir"), status: requiredOption(parsed, "status"), dataSchemaId: optionalOption(parsed, "data-schema-id") };
2737
+ }
2738
+ function runOverrideInput(parsed) {
2739
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), status: requiredOption(parsed, "status"), verdict: optionalOption(parsed, "verdict"), nextAction: optionalOption(parsed, "next-action"), manualOverrideReason: requiredOption(parsed, "reason") };
2740
+ }
2741
+ function stageCompletionInput(parsed) {
2742
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), status: requiredOption(parsed, "status"), stageReport: optionalOption(parsed, "stage-report") ?? optionalOption(parsed, "dashboard"), data: optionalOption(parsed, "data"), dataSchemaId: optionalOption(parsed, "data-schema-id"), report: optionalOption(parsed, "report"), aliases: allOptions(parsed, "alias") };
2743
+ }
2744
+ function runtimeServiceInput(parsed) {
2745
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), command: requiredOption(parsed, "command"), ports: requiredOption(parsed, "ports").split(","), readyPort: requiredOption(parsed, "ready-port"), readyPath: optionalOption(parsed, "ready-path") ?? "/", timeoutMs: optionalPositiveNumber(parsed, "ready-timeout-ms", true) ?? 30_000 };
2746
+ }
2747
+ function runtimeScopeResumeInput(parsed) {
2748
+ return { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") };
2749
+ }
2750
+ async function dispatchRuntime(context, command, parsed, progress, prepared = {}) {
2751
+ if (command === "scope") {
2752
+ if (parsed.positional.length === 1 && parsed.positional[0] === "reconcile") {
2753
+ if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2754
+ throw new AppError("usage", "Use runtime scope reconcile --scope-id ID --generation N --request-id ID", 2);
2755
+ return { ok: true, ...await reconcileRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id")) };
2756
+ }
2757
+ if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
2758
+ if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2759
+ throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
2760
+ return { ok: true, ...await launchRuntimeScopeResume(context, runtimeScopeResumeInput(parsed)) };
2761
+ }
2762
+ if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
2763
+ if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2764
+ throw new AppError("usage", "Use runtime scope control --scope-id ID --request-id ID --mode pause|stop [--manifest-json JSON]", 2);
2765
+ const mode = requiredOption(parsed, "mode");
2766
+ if (mode !== "pause" && mode !== "stop")
2767
+ throw new AppError("usage", "Scope control mode must be pause or stop", 2);
2768
+ return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, undefined, undefined, undefined, prepared.runtimeManifest) };
2769
+ }
2770
+ if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
2771
+ if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2772
+ throw new AppError("usage", "Invalid internal scope worker arguments", 2);
2773
+ return { ok: true, ...await serveRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation"))) };
2774
+ }
2775
+ if (parsed.positional.length === 1 && parsed.positional[0] === "stop") {
2776
+ if ([...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2777
+ throw new AppError("usage", "Usage: dd-flow runtime scope stop --scope-id EVAL_ID --request-id ID", 2);
2778
+ return { ok: true, ...await stopRuntimeScope(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
2779
+ }
2780
+ if (parsed.positional.length === 1 && parsed.positional[0] === "status") {
2781
+ if ([...parsed.options.keys()].some(key => key !== "scope-id") || [...parsed.options.values()].some(values => values.length !== 1))
2782
+ throw new AppError("usage", "Usage: dd-flow runtime scope status --scope-id EVAL_ID", 2);
2783
+ return { ok: true, ...runtimeBudgetStatus(context, requiredOption(parsed, "scope-id")) };
2784
+ }
2785
+ if (parsed.positional.length !== 1 || parsed.positional[0] !== "fence" || [...parsed.options.keys()].some(key => !["scope-id", "request-id"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
2786
+ throw new AppError("usage", "Usage: dd-flow runtime scope fence --scope-id EVAL_ID --request-id ID", 2);
2787
+ return { ok: true, ...fenceRuntimeBudget(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id")) };
2788
+ }
1587
2789
  if (command !== "process")
1588
- throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
2790
+ throw new AppError("usage", "Usage: dd-flow runtime <scope|process>; see dd-flow runtime scope --help", 2);
1589
2791
  const action = requiredPosition(parsed, 0, "runtime process action");
1590
- if (action === "register") {
1591
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1592
- const process = registerManagedProcess(context, {
1593
- kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
1594
- ownerPid: optionalPositiveNumber(parsed, "owner-pid", true),
1595
- projectId: optionalOption(parsed, "project-id") ?? null, runId: optionalOption(parsed, "run") ?? null,
1596
- workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
1597
- operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
1598
- stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {})
1599
- });
1600
- return { ok: true, process };
1601
- }
1602
- if (action === "confirm") {
1603
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1604
- const processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
1605
- return { ok: true, process: confirmManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId ? { processGroupId } : {}), ...(leaseMs ? { leaseMs } : {}) }) };
1606
- }
1607
- if (action === "heartbeat") {
1608
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1609
- return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
1610
- }
1611
- if (action === "finish") {
1612
- const state = requiredOption(parsed, "state");
1613
- if (state !== "stopped" && state !== "failed")
1614
- throw new AppError("validation", "--state must be stopped or failed", 2);
1615
- return { ok: finishManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state: state, reason: optionalOption(parsed, "reason") ?? null }) };
1616
- }
1617
- if (action === "status")
1618
- return { ok: true, processes: managedProcessStatus(context) };
1619
- if (action === "reconcile")
1620
- return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalPositiveNumber(parsed, "grace-ms", true)) };
1621
- throw new AppError("usage", "Usage: dd-flow runtime process <register|confirm|heartbeat|finish|status|reconcile>", 2);
2792
+ if (action === "start")
2793
+ return await startRuntimeService(context, { ...runtimeServiceInput(parsed), progress });
2794
+ return await dispatchRuntimeProcess(context, action, parsed, prepared);
1622
2795
  }
1623
2796
  function dispatchStat(context, command, parsed) {
1624
2797
  if (command === "usage") {
@@ -1652,7 +2825,7 @@ function dispatchCmux(context, parsed) {
1652
2825
  }
1653
2826
  throw new AppError("usage", `Unknown integration cmux action: ${action}`, 2);
1654
2827
  }
1655
- function dispatchDashboard(context, command, parsed) {
2828
+ async function dispatchDashboard(context, command, parsed) {
1656
2829
  const action = dashboardAction(command);
1657
2830
  if (command === "data") {
1658
2831
  const target = resolveDashboardTarget(context, {
@@ -1687,7 +2860,7 @@ function dispatchDashboard(context, command, parsed) {
1687
2860
  projectRoot: optionalOption(parsed, "project-root"),
1688
2861
  protocol: optionalOption(parsed, "protocol")
1689
2862
  });
1690
- return withDashboardGuidance(action, target, openDashboardForResolvedTarget(context, target, parsed));
2863
+ return withDashboardGuidance(action, target, await openDashboardForResolvedTarget(context, target, parsed));
1691
2864
  }
1692
2865
  if (command === "refresh") {
1693
2866
  const open = optionalOption(parsed, "open");
@@ -1701,7 +2874,7 @@ function dispatchDashboard(context, command, parsed) {
1701
2874
  protocol: optionalOption(parsed, "protocol"),
1702
2875
  all: hasOption(parsed, "all")
1703
2876
  });
1704
- return withDashboardGuidance(action, target, refreshDashboardForResolvedTarget(context, target, parsed, open));
2877
+ return withDashboardGuidance(action, target, await refreshDashboardForResolvedTarget(context, target, parsed, open));
1705
2878
  }
1706
2879
  if (command === "refresh-global") {
1707
2880
  const target = resolveDashboardTarget(context, { action: "refresh", global: true });
@@ -1805,7 +2978,9 @@ function dispatchSchema(context, command, parsed) {
1805
2978
  // searching project directories: run_id is a structural field in the
1806
2979
  // submitted artifact and the database owns its materialized run_root.
1807
2980
  const explicitRunId = optionalOption(parsed, "run");
1808
- const declaredRunId = explicitRunId ?? readRunId(file);
2981
+ const data = prepareJsonFile(file, "schema input").value;
2982
+ const declared = data && typeof data === "object" && "run_id" in data ? data.run_id : undefined;
2983
+ const declaredRunId = explicitRunId ?? (typeof declared === "string" && declared.trim() ? declared : undefined);
1809
2984
  const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
1810
2985
  const bound = projectRoot
1811
2986
  ? context.db.get("SELECT r.id, r.run_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE p.root = ? AND (r.id = ? OR r.run_root = ?) LIMIT 1", [fs.realpathSync(projectRoot), declaredRunId ?? "", fileRunRoot ?? ""])
@@ -1813,6 +2988,7 @@ function dispatchSchema(context, command, parsed) {
1813
2988
  return validateSchema({
1814
2989
  schemaName: requiredOption(parsed, "schema"),
1815
2990
  file,
2991
+ data,
1816
2992
  ...(schemaDir ? { schemaDir } : {}),
1817
2993
  ...(projectRoot ? { projectRoot } : {}),
1818
2994
  ...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
@@ -1822,14 +2998,11 @@ function dispatchSchema(context, command, parsed) {
1822
2998
  }
1823
2999
  throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
1824
3000
  }
1825
- function readRunId(file) {
1826
- try {
1827
- const value = JSON.parse(fs.readFileSync(file, "utf8"));
1828
- return typeof value.run_id === "string" && value.run_id.trim() ? value.run_id : undefined;
1829
- }
1830
- catch {
1831
- return undefined;
1832
- }
3001
+ function laneWorkspaceInput(parsed) {
3002
+ return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workspacePath: requiredOption(parsed, "path"), branch: optionalOption(parsed, "branch") };
3003
+ }
3004
+ function laneWaiterCancelInput(parsed) {
3005
+ return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"), reason: requiredOption(parsed, "reason") };
1833
3006
  }
1834
3007
  async function dispatchLane(context, command, parsed) {
1835
3008
  if (command === "status") {
@@ -1841,24 +3014,14 @@ async function dispatchLane(context, command, parsed) {
1841
3014
  if (command === "waiter") {
1842
3015
  const action = requiredPosition(parsed, 0, "lane waiter action");
1843
3016
  if (action === "cancel") {
1844
- return cancelLaneWaiter(context, {
1845
- projectRoot: requiredOption(parsed, "project-root"),
1846
- lane: requiredOption(parsed, "lane"),
1847
- workerId: requiredOption(parsed, "worker-id"),
1848
- reason: requiredOption(parsed, "reason")
1849
- });
3017
+ return cancelLaneWaiter(context, laneWaiterCancelInput(parsed));
1850
3018
  }
1851
3019
  throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
1852
3020
  }
1853
3021
  if (command === "workspace") {
1854
3022
  const action = requiredPosition(parsed, 0, "lane workspace action");
1855
3023
  if (action === "set") {
1856
- return setLaneWorkspace(context, {
1857
- projectRoot: requiredOption(parsed, "project-root"),
1858
- lane: requiredOption(parsed, "lane"),
1859
- workspacePath: requiredOption(parsed, "path"),
1860
- branch: optionalOption(parsed, "branch")
1861
- });
3024
+ return setLaneWorkspace(context, laneWorkspaceInput(parsed));
1862
3025
  }
1863
3026
  if (action === "check") {
1864
3027
  return checkLaneWorkspace(context, {
@@ -1930,7 +3093,7 @@ async function dispatchLane(context, command, parsed) {
1930
3093
  }
1931
3094
  throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
1932
3095
  }
1933
- function dispatchCodexHooks(context, parsed) {
3096
+ function dispatchCodexHooks(context, parsed, prepared) {
1934
3097
  const action = requiredPosition(parsed, 0, "codex hooks action");
1935
3098
  if (action === "print") {
1936
3099
  return printCodexHooks(context, {
@@ -1952,7 +3115,7 @@ function dispatchCodexHooks(context, parsed) {
1952
3115
  target: optionalOption(parsed, "target"),
1953
3116
  profile: optionalOption(parsed, "profile"),
1954
3117
  yes: hasOption(parsed, "yes")
1955
- });
3118
+ }, prepared.codexHooks);
1956
3119
  }
1957
3120
  if (action === "remove") {
1958
3121
  return removeCodexHooks(context, {
@@ -1960,11 +3123,14 @@ function dispatchCodexHooks(context, parsed) {
1960
3123
  target: optionalOption(parsed, "target"),
1961
3124
  profile: optionalOption(parsed, "profile"),
1962
3125
  yes: hasOption(parsed, "yes")
1963
- });
3126
+ }, prepared.codexHooks);
1964
3127
  }
1965
3128
  throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
1966
3129
  }
1967
- function dispatchCodexHome(context, parsed) {
3130
+ function codexHomeInput(parsed) {
3131
+ return { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), sourceHome: optionalOption(parsed, "source-home"), targetHome: optionalOption(parsed, "target-home") };
3132
+ }
3133
+ function dispatchCodexHome(context, parsed, prepared) {
1968
3134
  const action = requiredPosition(parsed, 0, "codex home action");
1969
3135
  if (action === "plan") {
1970
3136
  return planCodexHome(context, {
@@ -1975,12 +3141,7 @@ function dispatchCodexHome(context, parsed) {
1975
3141
  });
1976
3142
  }
1977
3143
  if (action === "init") {
1978
- return initCodexHome(context, {
1979
- projectRoot: requiredOption(parsed, "project-root"),
1980
- profile: optionalOption(parsed, "profile"),
1981
- sourceHome: optionalOption(parsed, "source-home"),
1982
- targetHome: optionalOption(parsed, "target-home")
1983
- });
3144
+ return initCodexHome(context, codexHomeInput(parsed), prepared.codexHome);
1984
3145
  }
1985
3146
  if (action === "status") {
1986
3147
  return getCodexHomeStatus(context, {
@@ -2003,23 +3164,6 @@ function dispatchCodexHome(context, parsed) {
2003
3164
  }
2004
3165
  throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
2005
3166
  }
2006
- function dispatchCodexHook(context, parsed, stdin) {
2007
- const action = requiredPosition(parsed, 0, "codex hook action");
2008
- if (action === "handle") {
2009
- const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
2010
- const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
2011
- const result = handleCodexHook(hookContext, {
2012
- event: requiredOption(parsed, "event"),
2013
- projectRoot: optionalOption(parsed, "project-root"),
2014
- stdin
2015
- });
2016
- if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
2017
- return { hookSpecificOutput: result.hookSpecificOutput };
2018
- }
2019
- return {};
2020
- }
2021
- throw new AppError("usage", `Unknown codex hook action: ${action}`, 2);
2022
- }
2023
3167
  function dispatchZcodeEvent(context, parsed, stdin) {
2024
3168
  const action = requiredPosition(parsed, 0, "zcode event action");
2025
3169
  if (action === "handle") {
@@ -2030,6 +3174,15 @@ function dispatchZcodeEvent(context, parsed, stdin) {
2030
3174
  }
2031
3175
  throw new AppError("usage", `Unknown zcode event action: ${action}`, 2);
2032
3176
  }
3177
+ function dispatchZcodeHook(context, parsed, stdin) {
3178
+ if (requiredPosition(parsed, 0, "zcode hook action") !== "handle")
3179
+ throw new AppError("usage", "Unknown zcode hook action", 2);
3180
+ const result = handleZcodeHook(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
3181
+ if (result && typeof result === "object" && !Array.isArray(result) && "hookSpecificOutput" in result) {
3182
+ return { hookSpecificOutput: result.hookSpecificOutput };
3183
+ }
3184
+ return {};
3185
+ }
2033
3186
  function dispatchGrokEvent(context, parsed, stdin) {
2034
3187
  const action = requiredPosition(parsed, 0, "grok event action");
2035
3188
  if (action === "handle") {
@@ -2053,7 +3206,7 @@ function dispatchAgyEvent(context, parsed, stdin) {
2053
3206
  return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
2054
3207
  throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
2055
3208
  }
2056
- function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
3209
+ function dispatchWorktree(context, command, parsed, scopeProjectRoot, prepared) {
2057
3210
  if (command === "plan") {
2058
3211
  return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
2059
3212
  }
@@ -2064,13 +3217,13 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
2064
3217
  branch: requiredOption(parsed, "branch"),
2065
3218
  base: requiredOption(parsed, "base"),
2066
3219
  path: requiredOption(parsed, "path")
2067
- });
3220
+ }, prepared.worktreeCreate);
2068
3221
  }
2069
3222
  if (command === "status") {
2070
3223
  return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
2071
3224
  }
2072
3225
  if (command === "bootstrap") {
2073
- return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
3226
+ return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot }, prepared.worktreeBootstrap);
2074
3227
  }
2075
3228
  if (command === "close") {
2076
3229
  return closeWorktree(context, {
@@ -2082,52 +3235,50 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
2082
3235
  throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
2083
3236
  }
2084
3237
  function dispatchPlanItem(context, parsed, projectRoot) {
3238
+ return updatePlanItem(context, planItemInput(parsed, projectRoot));
3239
+ }
3240
+ function planItemInput(parsed, projectRoot) {
2085
3241
  const action = requiredPosition(parsed, 0, "plan item action");
2086
3242
  const protocolId = requiredPosition(parsed, 1, "protocol-id");
2087
3243
  const itemId = requiredPosition(parsed, 2, "item-id");
2088
3244
  if (action === "start") {
2089
- return startPlanItem(context, { protocolId, projectRoot, itemId });
3245
+ return { protocolId, projectRoot, itemId, action };
2090
3246
  }
2091
3247
  if (action === "done") {
2092
- return completePlanItem(context, {
3248
+ return {
3249
+ action,
2093
3250
  protocolId,
2094
3251
  projectRoot,
2095
3252
  itemId,
2096
3253
  summary: requiredOption(parsed, "summary"),
2097
3254
  evidence: parsed.options.get("evidence") ?? []
2098
- });
3255
+ };
2099
3256
  }
2100
3257
  if (action === "block") {
2101
- return blockPlanItem(context, {
3258
+ return {
3259
+ action,
2102
3260
  protocolId,
2103
3261
  projectRoot,
2104
3262
  itemId,
2105
3263
  reason: requiredOption(parsed, "reason"),
2106
3264
  userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
2107
- });
3265
+ };
2108
3266
  }
2109
3267
  if (action === "skip") {
2110
- return skipPlanItem(context, {
3268
+ return {
3269
+ action,
2111
3270
  protocolId,
2112
3271
  projectRoot,
2113
3272
  itemId,
2114
3273
  reason: requiredOption(parsed, "reason")
2115
- });
3274
+ };
2116
3275
  }
2117
3276
  throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
2118
3277
  }
2119
3278
  function readOptionalJsonFile(file) {
2120
3279
  if (!file)
2121
3280
  return undefined;
2122
- if (!fs.existsSync(file)) {
2123
- throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
2124
- }
2125
- try {
2126
- return JSON.parse(fs.readFileSync(file, "utf8"));
2127
- }
2128
- catch {
2129
- throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
2130
- }
3281
+ return prepareJsonFile(file, "JSON input").value;
2131
3282
  }
2132
3283
  function parseFlowFlagOverrides(values) {
2133
3284
  const result = {};
@@ -2155,29 +3306,17 @@ function parseArgs(args) {
2155
3306
  function requiredPosition(parsed, index, label) {
2156
3307
  const value = parsed.positional[index];
2157
3308
  if (!value) {
2158
- throw new AppError("usage", `Missing required argument: ${label}`, 2);
3309
+ throw new AppError("usage", `Missing required argument: ${label}`, 2, { parameter: label, reason: "missing_required_argument" });
2159
3310
  }
2160
3311
  return value;
2161
3312
  }
2162
- function optionalOption(parsed, key) {
2163
- const values = parsed.options.get(key);
2164
- const value = values?.[values.length - 1];
2165
- return value === flagValue ? undefined : value;
2166
- }
2167
3313
  function allOptions(parsed, key) {
2168
3314
  return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
2169
3315
  }
2170
- function requiredOption(parsed, key) {
2171
- const value = optionalOption(parsed, key);
2172
- if (!value) {
2173
- throw new AppError("usage", `Missing required option: --${key}`, 2);
2174
- }
2175
- return value;
2176
- }
2177
3316
  function requiredScopeProjectRoot(root, operation) {
2178
3317
  if (root)
2179
3318
  return root;
2180
- throw new AppError("usage", `${operation} requires --project-root or a unique protocol context`, 2);
3319
+ throw new AppError("usage", `${operation} requires --project-root or a unique protocol context`, 2, { parameter: "project-root", reason: "missing_required_option" });
2181
3320
  }
2182
3321
  function requiredOptionOrScope(parsed, root, key) {
2183
3322
  return root ?? requiredOption(parsed, key);
@@ -2196,7 +3335,7 @@ function requiredOptionAlias(parsed, keys) {
2196
3335
  if (value)
2197
3336
  return value;
2198
3337
  }
2199
- throw new AppError("usage", `Missing required option: ${keys.map((key) => `--${key}`).join(" or ")}`, 2);
3338
+ throw new AppError("usage", `Missing required option: ${keys.map((key) => `--${key}`).join(" or ")}`, 2, { parameter: [...keys].sort(), reason: "missing_required_option" });
2200
3339
  }
2201
3340
  function hasOption(parsed, key) {
2202
3341
  return parsed.options.has(key);
@@ -2213,26 +3352,6 @@ function parseBoolean(value, key) {
2213
3352
  function parseOptionalBoolean(value, key) {
2214
3353
  return typeof value === "string" ? parseBoolean(value, key) : undefined;
2215
3354
  }
2216
- function optionalNumber(parsed, key) {
2217
- const value = optionalOption(parsed, key);
2218
- if (typeof value === "undefined") {
2219
- return undefined;
2220
- }
2221
- const parsedNumber = Number(value);
2222
- if (!Number.isFinite(parsedNumber)) {
2223
- throw new AppError("validation", `--${key} must be a number`, 2);
2224
- }
2225
- return parsedNumber;
2226
- }
2227
- function optionalPositiveNumber(parsed, key, integer = false) {
2228
- const value = optionalNumber(parsed, key);
2229
- if (typeof value === "undefined")
2230
- return undefined;
2231
- if (value <= 0 || (integer && !Number.isInteger(value))) {
2232
- throw new AppError("validation", `--${key} must be a positive${integer ? " integer" : ""} number`, 2);
2233
- }
2234
- return value;
2235
- }
2236
3355
  function requiredWorkerId(parsed) {
2237
3356
  return requiredOption(parsed, "worker-id");
2238
3357
  }
@@ -2246,11 +3365,13 @@ function parseWorktreePolicy(value) {
2246
3365
  }
2247
3366
  throw new AppError("validation", "--worktree must be keep or remove", 2);
2248
3367
  }
2249
- async function readStdin(stream) {
3368
+ async function readStdin(stream, maxBytes) {
2250
3369
  if (!stream) {
2251
3370
  return "";
2252
3371
  }
2253
3372
  let data = "";
3373
+ if (maxBytes !== undefined)
3374
+ return readBoundedUtf8Stream(stream, maxBytes);
2254
3375
  stream.setEncoding("utf8");
2255
3376
  for await (const chunk of stream) {
2256
3377
  data += chunk;
@@ -2266,7 +3387,8 @@ function refreshDashboardsAfterMutation(context, args, result, resolvedScope) {
2266
3387
  const projectRoot = projectRootForMutation(context, args, result, resolvedScope);
2267
3388
  if (!projectRoot)
2268
3389
  return [];
2269
- autoRefreshDashboards(context, { projectRoot });
3390
+ const runId = result && typeof result === "object" && "run_id" in result && typeof result.run_id === "string" ? result.run_id : undefined;
3391
+ autoRefreshDashboards(context, { projectRoot, ...(runId ? { runId } : {}) });
2270
3392
  return [];
2271
3393
  }
2272
3394
  catch (error) {
@@ -2328,7 +3450,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
2328
3450
  return projectRootForMergeQueueMutation(context, command, parsed);
2329
3451
  }
2330
3452
  if (family === "merge" && ["one-shot", "apply", "repair", "request"].includes(command ?? "")) {
2331
- return requiredOption(parsed, "project-root");
3453
+ return resolvedScope ?? requiredOption(parsed, "project-root");
2332
3454
  }
2333
3455
  if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
2334
3456
  return requiredOption(parsed, "project-root");
@@ -2346,7 +3468,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
2346
3468
  return requiredOption(parsed, "project-root");
2347
3469
  }
2348
3470
  if (family === "stage" && ["start", "pause", "resume", "finish"].includes(command ?? "")) {
2349
- return requiredOption(parsed, "project-root");
3471
+ return resolvedScope ?? requiredOption(parsed, "project-root");
2350
3472
  }
2351
3473
  if (family === "worktree" && ["create", "bootstrap", "close"].includes(command ?? "")) {
2352
3474
  return protocolProjectRoot(context, requiredOption(parsed, "protocol-id"));
@@ -2360,7 +3482,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
2360
3482
  if (family === "opencode" && ((command === "event" && requiredPosition(parsed, 0, "opencode event action") === "handle") || command === "usage")) {
2361
3483
  return requiredOption(parsed, "project-root");
2362
3484
  }
2363
- if (family === "agy" && ((command === "event" && requiredPosition(parsed, 0, "agy event action") === "handle") || command === "usage"))
3485
+ if (["agy", "droid"].includes(family ?? "") && ((command === "event" && requiredPosition(parsed, 0, `${family} event action`) === "handle") || command === "usage"))
2364
3486
  return requiredOption(parsed, "project-root");
2365
3487
  if (family === "codex" && command === "hooks") {
2366
3488
  const action = requiredPosition(parsed, 0, "codex hooks action");
@@ -2397,14 +3519,36 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
2397
3519
  function protocolProjectRoot(context, protocolId) {
2398
3520
  return requireProtocol(context, protocolId).project_root;
2399
3521
  }
3522
+ function prepareCliWorkRepair(context, parsed, objective, scopeProjectRoot) {
3523
+ const origins = parsed.options.get("origin-work") ?? [], findings = parsed.options.get("from-finding") ?? [], unresolved = parsed.options.get("from-unresolved") ?? [], receipts = parsed.options.get("from-check") ?? [];
3524
+ if ([findings.length > 0, unresolved.length > 0, receipts.length > 0].filter(Boolean).length !== 1)
3525
+ throw new AppError("usage", "Repair requires exactly one source: --from-check, --from-finding or --from-unresolved", 2);
3526
+ const scope = { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"), runId: requiredOption(parsed, "run"), objective };
3527
+ if (findings.length) {
3528
+ if (origins.length || hasOption(parsed, "verification-file"))
3529
+ throw new AppError("usage", "Finding repair derives its origins and cannot accept --origin-work or --verification-file", 2);
3530
+ return prepareVnextCodeReviewRepair(context, { ...scope, findingIds: findings, checkRefsByFinding: Object.fromEntries(findings.map(finding => [finding, parsed.options.get("check-ref") ?? []])) });
3531
+ }
3532
+ if (!origins.length)
3533
+ throw new AppError("usage", "Repair requires at least one --origin-work", 2);
3534
+ if (hasOption(parsed, "check-ref"))
3535
+ throw new AppError("usage", "--check-ref is only supported with --from-finding", 2);
3536
+ if (receipts.length > 1)
3537
+ throw new AppError("usage", "Create one repair Work per --from-check receipt", 2);
3538
+ if (!unresolved.length && hasOption(parsed, "verification-file"))
3539
+ throw new AppError("usage", "--verification-file is only supported with --from-unresolved", 2);
3540
+ const input = { ...scope, originWorkIds: origins, ...(unresolved.length ? { semanticUnresolved: unresolved, verificationPath: requiredOption(parsed, "verification-file") } : { checkReceiptId: receipts[0] }) };
3541
+ return { input, value: prepareVnextCodeRepair(context, input) };
3542
+ }
3543
+ function workerPromptInput(parsed) {
3544
+ const workspaceRoot = optionalOption(parsed, "workspace-root"), taskFile = optionalOption(parsed, "task-file");
3545
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredOption(parsed, "run"), stage: requiredOption(parsed, "stage"), profile: requiredOption(parsed, "profile"), ...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }), ...(workspaceRoot ? { workspaceRoot } : {}) };
3546
+ }
2400
3547
  function runnerIntake(context) {
2401
3548
  const input = context.value.task_input?.[0];
2402
3549
  if (!input?.path)
2403
3550
  throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
2404
- const file = path.resolve(input.path);
2405
- if (!fs.existsSync(file) || !fs.statSync(file).isFile())
2406
- throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
2407
- return fs.readFileSync(file, "utf8");
3551
+ return prepareTextFile(input.path, "Runner SPECIFY task input").text;
2408
3552
  }
2409
3553
  /** Stage services install context before their lifecycle transition. Keeping
2410
3554
  * this check here makes an incomplete service migration fail closed instead of