@deksden-com/dd-flow-cli 0.9.0-beta.15 → 0.9.0-beta.150

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