@deksden-com/dd-flow-cli 0.9.0-beta.74 → 0.9.0-beta.79

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 (70) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/build-info.json +6 -6
  3. package/dist/cli/command-inputs.js +300 -0
  4. package/dist/cli/help.js +1 -1
  5. package/dist/cli/hook-ingress.js +82 -0
  6. package/dist/cli/input-preparation.js +115 -0
  7. package/dist/cli/run-cli.js +1015 -423
  8. package/dist/cli.js +6 -2
  9. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  10. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +7 -2
  11. package/dist/harness-runtime/lib/dd-codex.mjs +137 -13
  12. package/dist/harness-runtime/lib/dd-droid.mjs +3 -2
  13. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +1 -1
  14. package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
  15. package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
  16. package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
  17. package/dist/services/canon.js +11 -8
  18. package/dist/services/cleanup.js +49 -8
  19. package/dist/services/cli-operation-classifier.js +12 -24
  20. package/dist/services/codex-hook-delivery.js +28 -0
  21. package/dist/services/config.js +11 -8
  22. package/dist/services/controller-fanout.js +7 -11
  23. package/dist/services/dashboard.js +94 -31
  24. package/dist/services/engines.js +75 -61
  25. package/dist/services/eval-snapshots.js +138 -38
  26. package/dist/services/execution-policy.js +5 -12
  27. package/dist/services/hooks.js +226 -203
  28. package/dist/services/lanes.js +60 -52
  29. package/dist/services/lifecycle-command.js +13 -1
  30. package/dist/services/lifecycle-invocations.js +285 -94
  31. package/dist/services/managed-processes.js +158 -18
  32. package/dist/services/merge-queue.js +173 -98
  33. package/dist/services/migrations.js +13 -8
  34. package/dist/services/plan-runtime.js +19 -8
  35. package/dist/services/plans.js +28 -25
  36. package/dist/services/projects.js +11 -2
  37. package/dist/services/prompts.js +23 -16
  38. package/dist/services/protocols.js +54 -19
  39. package/dist/services/recovery-observation-budget.js +1 -1
  40. package/dist/services/run-controller-adapter.js +31 -20
  41. package/dist/services/run-controller-state.js +5 -0
  42. package/dist/services/run-controller.js +102 -56
  43. package/dist/services/run-fork.js +36 -22
  44. package/dist/services/run-recovery.js +32 -17
  45. package/dist/services/runs.js +110 -38
  46. package/dist/services/runtime-budget.js +120 -48
  47. package/dist/services/runtime-scope-capture.js +2 -2
  48. package/dist/services/runtime-scope-control.js +2 -2
  49. package/dist/services/runtime-scope-resume.js +34 -12
  50. package/dist/services/runtime-scope-worker.js +49 -16
  51. package/dist/services/runtime-service.js +8 -7
  52. package/dist/services/schema-validation.js +9 -7
  53. package/dist/services/sessions.js +35 -69
  54. package/dist/services/stage-blocker.js +17 -7
  55. package/dist/services/stage-context.js +42 -16
  56. package/dist/services/stage-lifecycle.js +82 -100
  57. package/dist/services/stage-pause.js +54 -24
  58. package/dist/services/vnext-code-review.js +82 -32
  59. package/dist/services/vnext-code.js +78 -45
  60. package/dist/services/vnext-fanout.js +13 -12
  61. package/dist/services/vnext-merge.js +68 -35
  62. package/dist/services/vnext-plan-review.js +85 -42
  63. package/dist/services/vnext-plan.js +91 -70
  64. package/dist/services/vnext-protocolize.js +116 -50
  65. package/dist/services/vnext-specify.js +70 -57
  66. package/dist/services/work-registry.js +207 -94
  67. package/dist/services/worktrees.js +85 -35
  68. package/dist/storage/database.js +104 -10
  69. package/dist/storage/paths.js +17 -4
  70. package/package.json +1 -1
@@ -3,71 +3,77 @@ import path from "node:path";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { quote } from "shell-quote";
5
5
  import { observeLifecycleCommand } from "../services/lifecycle-invocations.js";
6
- import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, issueLifecycleInvocation, lifecycleInvocationScope, settleLifecycleInvocation, settleLifecycleRejection } from "../services/lifecycle-invocations.js";
6
+ import { verifyCodexHookDelivery } from "../services/codex-hook-delivery.js";
7
+ import { assertLifecycleInvocationCurrent, awaitLifecycleInvocation, expandLifecycleInvocationArgs, issueLifecycleInvocation, prepareLifecycleIssuance, lifecycleInvocationScope, observedLifecycleInvocation, settleLifecycleInvocation, settleLifecycleRejection } from "../services/lifecycle-invocations.js";
7
8
  import { createContext, createRouterContext } from "../runtime/context.js";
8
9
  import { migrateStoreWriter } from "../storage/writer-migration.js";
9
10
  import { helpForArgs } from "./help.js";
10
11
  import { AppError, isAppError } from "../shared/errors.js";
11
12
  import { writeJson } from "../shared/json.js";
12
- import { archiveProject, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
13
- import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, requireProtocol, syncProtocolFromRun, transitionProtocol } from "../services/protocols.js";
14
- import { blockPlanItem, completePlanItem, getPlanStatus, skipPlanItem, startPlanItem } from "../services/plans.js";
15
- import { cancelMergeQueueJob, claimNextMergeJob, claimMergeJob, claimMergeBundle, completeMergeBundle, completeMergeJob, failMergeBundle, failMergeJob, getMergeBundleStatus, getMergeQueueStatus, noteMergeJob, waitNextMergeJob } from "../services/merge-queue.js";
13
+ import { archiveProject, prepareProjectArchive, getProjectStatus, migrateProjectIds, registerProject, resolveProject } from "../services/projects.js";
14
+ import { cancelProtocol, getProtocolBlockers, getProtocolStatus, getReadyProtocols, implementProtocol, readyForMerge, registerProtocol, prepareProtocolRegistration, requireProtocol, syncProtocolFromRun, prepareProtocolTransition, transitionProtocol } from "../services/protocols.js";
15
+ import { getPlanStatus, preparePlanItemMutation, updatePlanItem } from "../services/plans.js";
16
+ 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";
16
17
  import { getMergeWorkerStatus, oneShotMergeClaim, startMergeWorker, stopProjectMergeWorker } from "../services/merge-worker.js";
17
- import { cleanupApply, cleanupScan } from "../services/cleanup.js";
18
- import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleDroidEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeHook, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, removeCodexHooks } from "../services/hooks.js";
19
- import { bootstrapWorktree, closeWorktree, createWorktreeRecord, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
20
- import { acquireLaneLock, cancelLaneWaiter, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
21
- import { getProjectConfigStatus, setProjectConfigValue } from "../services/config.js";
22
- import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
18
+ import { cleanupApply, cleanupScan, prepareCleanupApply } from "../services/cleanup.js";
19
+ import { getCodexHomeStatus, getCodexHooksStatus, handleCodexHook, handleAgyEvent, handleDroidEvent, handleGrokEvent, handleOpenCodeEvent, handleZcodeHook, handleZcodeEvent, isolatedDdFlowHomeFromHookStdin, claimBootstrapHookEvent, findRecentMatchingHookEvent, stageStartMatchKey, initCodexHome, prepareCodexHome, prepareCodexHooks, installCodexHooks, planCodexHome, printCodexHomeEnv, printCodexHooks, removeCodexHome, prepareCodexHomeRemoval, removeCodexHooks } from "../services/hooks.js";
20
+ import { bootstrapWorktree, closeWorktree, createWorktreeRecord, prepareWorktreeCreate, prepareWorktreeBootstrap, prepareWorktreeClose, getWorktreeStatus, planWorktree } from "../services/worktrees.js";
21
+ import { acquireLaneLock, cancelLaneWaiter, prepareLaneWaiterCancellation, checkLaneWorkspace, getLaneWaiters, getLaneStatus, heartbeatLaneLock, releaseLaneLock, setLaneWorkspace, prepareLaneWorkspace, prepareLaneLock, waitAcquireLaneLock, waitForLaneLock } from "../services/lanes.js";
22
+ import { getProjectConfigStatus, prepareProjectConfigValue, setProjectConfigValue } from "../services/config.js";
23
+ import { getCanonStatus, registerCanonRoot, resolveCanonRoot, prepareCanonRegistration } from "../services/canon.js";
23
24
  import { getRuntimeStatus } from "../services/status.js";
24
25
  import { getCliVersionReport } from "../services/build-info.js";
25
26
  import { parseCommandArgs } from "../services/lifecycle-command.js";
26
- import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
27
+ import { prepareError, prepareJsonFile, prepareJsonSchema, prepareJsonText, prepareTextFile } from "./input-preparation.js";
28
+ import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, prepareDashboardRefresh, prepareDashboardRender, prepareGlobalDashboardRender, prepareDashboardOptions, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
27
29
  import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
28
- import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
30
+ import { getFlowSessionStatus, prepareFlowSessionPayload, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
29
31
  import { getProtocolBranchContext } from "../services/branch-context.js";
30
- import { attachFlowRunStage, completeFlowRun, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, setFlowRunVariable, startFlowRun } from "../services/runs.js";
32
+ import { attachFlowRunStage, prepareFlowRunStageAttachment, prepareFlowRunStageCompletion, completeFlowRun, prepareFlowRunCompletion, completeFlowRunStage, getFlowRunSessions, getFlowRunTimeline, getStoredFlowRunUsage, getFlowRunFlagsStatus, getFlowRunStatus, getFlowRunConfig, getFlowRunVariables, listFlowRuns, reviseFlowRunFlags, setFlowRunConfig, prepareFlowRunConfig, prepareFlowRunVariable, setFlowRunVariable, startFlowRun, prepareFlowRunStart } from "../services/runs.js";
31
33
  import { previewNextEntityId } from "../services/ids.js";
32
34
  import { validateSchema } from "../services/schema-validation.js";
33
35
  import { preflightMemoryPermissions } from "../services/memory-permissions.js";
34
36
  import { ingestDroidUsage, ingestAgyUsage, ingestGrokUsage, ingestOpenCodeUsage, ingestZcodeUsage } from "../services/usage.js";
35
- import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, verifyMigrationReport } from "../services/migrations.js";
37
+ import { applyMigration, assessMigrationImpact, assertUpgradeRunStorageReady, planMigration, prepareMigrationApply, verifyMigrationReport } from "../services/migrations.js";
36
38
  import { publishProjectSummary } from "../services/project-summary.js";
37
39
  import { requireProjectByRoot } from "../services/projects.js";
38
40
  import { resolveProjectRoot } from "../storage/paths.js";
39
- import { doctorEngines, bindRunEngine, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
41
+ import { doctorEngines, bindRunEngine, prepareRunEngineBind, engineInfo, installCurrentEngine, listEngines, resolveEngine, resolveExplicitProjectRoot, resolveOperationProjectRoot, routeArgsThroughEngine } from "../services/engines.js";
40
42
  import { preflightCliCompatibility } from "../services/compatibility-preflight.js";
41
- import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify } from "../services/vnext-specify.js";
42
- import { finishVnextProtocolize, isVnextProtocolizeRun, startVnextProtocolize } from "../services/vnext-protocolize.js";
43
- import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vnext-plan.js";
44
- import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
45
- import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
46
- import { addVnextCodeReviewRepair, finishVnextCodeReview, isVnextCodeReviewRun, startVnextCodeReview } from "../services/vnext-code-review.js";
47
- import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
43
+ import { finishVnextSpecifyStage, getVnextWorkStatus, isVnextSpecifyFlow, isVnextSpecifyRun, launchVnextSpecify, prepareVnextSpecifyStart } from "../services/vnext-specify.js";
44
+ import { finishVnextProtocolize, isVnextProtocolizeRun, prepareProtocolizeResultPath, prepareVnextProtocolizeStart, startVnextProtocolize } from "../services/vnext-protocolize.js";
45
+ import { finishVnextPlan, isVnextPlanRun, startVnextPlan, prepareVnextPlanStart, prepareVnextPlanFinish } from "../services/vnext-plan.js";
46
+ import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, prepareVnextPlanReviewStart, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
47
+ import { addVnextCodeRepair, finishVnextCode, prepareVnextCodeRepair, prepareVnextCodeStart, startVnextCode } from "../services/vnext-code.js";
48
+ import { finishVnextCodeReview, isVnextCodeReviewRun, prepareCodeReviewDecision, prepareVnextCodeReviewRepair, prepareVnextCodeReviewStart, startVnextCodeReview } from "../services/vnext-code-review.js";
49
+ import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, prepareVnextMergeFinish, prepareVnextMergeStart, repairVnextMerge, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
48
50
  import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
49
- import { getVnextFanoutStatus, observeVnextNativeChildren, reconcileVnextFanout } from "../services/vnext-fanout.js";
50
- import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
51
- import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
51
+ import { getVnextFanoutStatus, observeVnextNativeChildren, prepareNativeObservations, reconcileVnextFanout } from "../services/vnext-fanout.js";
52
+ import { pauseStageForUser, resumeStageAfterUser, prepareStagePause, prepareStageResume } from "../services/stage-pause.js";
53
+ import { blockStageForRuntime, unblockStageAfterRuntimeRepair, prepareStageRuntimeBlock, prepareStageRuntimeUnblock } from "../services/stage-blocker.js";
52
54
  import { classifyCliOperation, requiresExplicitUpgradeAuthorization } from "../services/cli-operation-classifier.js";
53
- import { renderWorkerPrompt } from "../services/prompts.js";
54
- import { finishStage, startStage } from "../services/stage-lifecycle.js";
55
- import { addWorkBatch, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, retryWork, showWork, startWork } from "../services/work-registry.js";
55
+ import { prepareWorkerPrompt, renderWorkerPrompt } from "../services/prompts.js";
56
+ import { finishStage, prepareStageFinish, startStage, prepareLegacyStageStart } from "../services/stage-lifecycle.js";
57
+ import { addWorkBatch, prepareWorkBatch, prepareWorkDeps, prepareWorkDelete, prepareWorkSettlement, prepareWorkRetry, assertStageLifecycleOwner, assertWorkLifecycleOwner, cancelWork, deleteWork, failWork, finishWork, listWorks, mutateWorkDeps, prepareWorkResult, retryWork, showWork, startWork } from "../services/work-registry.js";
56
58
  import { launchExternalWork } from "../services/external-work-launch.js";
57
- import { createEvalBootstrapSnapshot, createEvalRunSnapshot, prepareVnextSpecifyRun, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
58
- import { forkRunFromSnapshot } from "../services/run-fork.js";
59
- import { acceptRunRecovery, assertRunMutationAllowed, beginRunRecovery, inspectRunRecovery, recordRecoveryCapture, recoveryGuard, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
59
+ import { createEvalBootstrapSnapshot, prepareEvalBootstrapSnapshot, createEvalRunSnapshot, prepareEvalRunSnapshotCreation, prepareVnextSpecifyRun, prepareEvalRunRestore, prepareEvalBootstrapRestore, restoreEvalBootstrapSnapshot, restoreEvalRunSnapshot } from "../services/eval-snapshots.js";
60
+ import { forkRunFromSnapshot, prepareRunFork } from "../services/run-fork.js";
61
+ import { acceptRunRecovery, assertRunMutationAllowed, beginRunRecovery, inspectRunRecovery, prepareRecoveryCapture, prepareRecoveryPayload, prepareRunRecoveryResume, recordRecoveryCapture, recoveryGuard, resumeRunRecovery, sealRunRecovery } from "../services/run-recovery.js";
60
62
  import { recoveryRuntimeAdmission } from "../services/run-recovery-runtime.js";
61
- import { loadExternalStageContext } from "../services/stage-context.js";
62
- import { stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
63
- import { fenceRuntimeBudget, runtimeBudgetStatus, admitRuntimeCheck, parseRuntimeBudget, reserveProviderTurn, releaseProviderTurns } from "../services/runtime-budget.js";
63
+ import { loadExternalStageContext, prepareExternalStageContext } from "../services/stage-context.js";
64
+ import { vnextStageDirectory } from "../domain/stage-catalog.js";
65
+ import { prepareCommandRoute } from "./command-inputs.js";
66
+ import { parseLifecycleCommand } from "../services/lifecycle-command.js";
67
+ import { prepareManagedProcessRegistration, prepareManagedProcessConfirmation, prepareManagedProcessHeartbeat, prepareManagedProcessFinish, prepareManagedProcessStopOperation, prepareManagedProcessReconcile, stopManagedProcess, confirmManagedProcess, finishManagedProcess, heartbeatManagedProcess, managedProcessRuntimeHome, managedProcessStatus, processIsAlive, reconcileExpiredManagedProcesses, registerManagedProcess } from "../services/managed-processes.js";
68
+ import { fenceRuntimeBudget, runtimeBudgetStatus, admitRuntimeCheck, parseRuntimeBudget, prepareRuntimeScopeControl, prepareRuntimeScopeFence, reserveProviderTurn, releaseProviderTurns, prepareProviderTurnRelease } from "../services/runtime-budget.js";
64
69
  import { stopRuntimeScope } from "../services/runtime-scope-stop.js";
65
- import { launchRuntimeScopeControl, launchRuntimeScopeResume, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
66
- import { launchRunController, resumeRunController, serveRunController, runControllerStatus, answerRunController, supplyRunControllerContext } from "../services/run-controller.js";
70
+ import { launchRuntimeScopeControl, launchRuntimeScopeResume, prepareRuntimeScopeReconciliation, prepareRuntimeScopeServing, reconcileRuntimeScopeControl, serveRuntimeScopeControl } from "../services/runtime-scope-worker.js";
71
+ import { prepareRuntimeScopeResumeRequest } from "../services/runtime-scope-resume.js";
72
+ import { launchRunController, prepareRunControllerLaunch, resumeRunController, serveRunController, runControllerStatus, answerRunController, prepareRunControllerAnswer, supplyRunControllerContext, prepareRunControllerContext } from "../services/run-controller.js";
67
73
  import { controllerHitlAdmission } from "../services/run-controller-adapter.js";
68
74
  import { controlLifecycleReply, runControlStatus } from "../services/run-control.js";
69
75
  import { reconcileRunControl, requestRunControl, serveRunControl } from "../services/run-control-worker.js";
70
- import { startRuntimeService } from "../services/runtime-service.js";
76
+ import { startRuntimeService, prepareRuntimeService } from "../services/runtime-service.js";
71
77
  const defaultIo = {
72
78
  stdout: process.stdout,
73
79
  stderr: process.stderr,
@@ -103,7 +109,7 @@ export async function runCli(args, io = defaultIo, env = process.env) {
103
109
  io.stdout.write(`${help}\n`);
104
110
  return 0;
105
111
  }
106
- assertSingletonOptions(output.args);
112
+ const suppliedInvocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
107
113
  if (output.args[0] === "run" && ["drive", "control"].includes(output.args[1] ?? "")) {
108
114
  const controlArgs = parseArgs(output.args.slice(2));
109
115
  requiredOption(controlArgs, "run");
@@ -121,13 +127,32 @@ export async function runCli(args, io = defaultIo, env = process.env) {
121
127
  });
122
128
  }
123
129
  const routerContext = createRouterContext(env);
130
+ const publicCommand = quote(["dd-flow", ...output.args]);
131
+ const lifecycleCommand = parseLifecycleCommand(publicCommand);
132
+ const observedInvocation = observedLifecycleInvocation(routerContext, publicCommand);
133
+ if (!observedInvocation && !suppliedInvocationId && env.DD_FLOW_DAEMON_ID && lifecycleCommand.kind === "standalone") {
134
+ throw new AppError("invocation_receipt_missing", "Managed lifecycle command has no committed native receipt", 1, { effect: "no_effect", recoverable: false });
135
+ }
136
+ const invocationId = observedInvocation?.id ?? suppliedInvocationId;
137
+ const boundInvocationArgs = invocationId && invocationId !== suppliedInvocationId
138
+ ? [...output.args.filter((_, index, values) => values[index - 1] !== "--invocation-id" && values[index] !== "--invocation-id"), "--invocation-id", invocationId]
139
+ : output.args;
140
+ const observedOperation = lifecycleCommand.kind === "standalone" ? lifecycleCommand.invocation.operation : null;
141
+ if (observedInvocation && !observedOperation)
142
+ throw new AppError("invocation_command_invalid", "Observed lifecycle command could not be parsed", 1);
143
+ const invocationScope = invocationId ? lifecycleInvocationScope({ ...routerContext, env }, invocationId) : undefined;
144
+ const invocationArgs = invocationScope && observedOperation
145
+ ? expandLifecycleInvocationArgs(routerContext, boundInvocationArgs, invocationScope, observedOperation)
146
+ : boundInvocationArgs;
124
147
  emitHumanProgress(io, output, progress, "start");
125
148
  if (output.progressJsonl) {
126
149
  writeProgressJsonl(io, "start", `Starting ${output.args.slice(0, 3).join(" ")}`);
127
150
  heartbeat = setInterval(() => writeProgressJsonl(io, "running", "Command is still running"), 15_000);
128
151
  }
129
- const scopeProjectRoot = resolveProjectRootBeforeRouting(output.args, env);
130
- const routedArgs = output.responseFile ? [...output.args, "--response-file", output.responseFile] : output.args;
152
+ // Route by retained authority, so a mistyped project path reaches the CLI
153
+ // validator instead of selecting a different engine or failing preflight.
154
+ const scopeProjectRoot = invocationScope?.projectRoot ?? resolveProjectRootBeforeRouting(output.args, env);
155
+ const routedArgs = output.responseFile ? [...invocationArgs, "--response-file", output.responseFile] : invocationArgs;
131
156
  const routed = await routeArgsThroughEngine(routerContext, output.json ? [...routedArgs, "--json", ...(output.progressJsonl ? ["--progress-jsonl"] : [])] : routedArgs, io, io.stdin, env, scopeProjectRoot);
132
157
  if (routed?.routed && !routed.inProcess) {
133
158
  if (heartbeat)
@@ -137,19 +162,31 @@ export async function runCli(args, io = defaultIo, env = process.env) {
137
162
  writeProgressJsonl(io, "failed", "Routed engine failed", { exit_code: routed.exitCode });
138
163
  return routed.exitCode;
139
164
  }
165
+ let preparedInput;
166
+ let preparedRoute;
167
+ if (!invocationId) {
168
+ const preparationContext = createContext({ ...env }, "read_existing");
169
+ try {
170
+ preparedInput = await prepareCliInput(invocationArgs, preparationContext, io, scopeProjectRoot, output.responseFile);
171
+ }
172
+ finally {
173
+ preparationContext.db.close?.();
174
+ }
175
+ }
140
176
  const dispatchContext = routed?.inProcess
141
- ? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(output.args, classification))
142
- : createContext({ ...env }, databaseModeForCommand(output.args, classification));
177
+ ? createContext({ ...env, DD_FLOW_ENGINE_MODE: "1", DD_FLOW_ENGINE_HOME: "in-process", DD_FLOW_ROUTED_FROM: getCliVersionReport().cli.version }, databaseModeForCommand(invocationArgs, classification))
178
+ : createContext({ ...env }, databaseModeForCommand(invocationArgs, classification));
143
179
  diagnosticHome = dispatchContext.ddFlowHome;
144
- preflightCliCompatibility(dispatchContext, output.args, env, scopeProjectRoot);
145
180
  dispatchContext.env.DD_FLOW_INVOCATION_READONLY = classification.mode === "read_only_diagnostics" ? "1" : undefined;
146
- let dispatchArgs = output.args;
147
- settleHook = observeLifecycleCommand(dispatchContext, quote(["dd-flow", ...dispatchArgs]));
181
+ let dispatchArgs = invocationArgs;
148
182
  let retainedOutcome;
149
- const invocationId = optionalOption(parseArgs(output.args.slice(2)), "invocation-id");
183
+ // Issued transports may deliver their receipt asynchronously. Observe only
184
+ // after admission; retained outcomes must not need a fresh hook receipt.
150
185
  if (invocationId) {
151
- const command = quote(["dd-flow", ...output.args]);
186
+ const command = quote(["dd-flow", ...invocationArgs]);
152
187
  const admission = await awaitLifecycleInvocation(dispatchContext, { id: invocationId, command,
188
+ validateArgs: () => { preparedRoute = prepareCommandRoute(invocationArgs); },
189
+ prepare: async () => { preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile, preparedRoute); },
153
190
  assertCurrent: scope => assertLifecycleInvocationCurrent(dispatchContext, scope, command) });
154
191
  if (admission.replay) {
155
192
  retainedOutcome = admission.outcome;
@@ -158,7 +195,8 @@ export async function runCli(args, io = defaultIo, env = process.env) {
158
195
  }
159
196
  else {
160
197
  invocationSettlement = { context: dispatchContext, id: invocationId };
161
- dispatchArgs = [...output.args, "--hook-event-id", admission.eventKey];
198
+ dispatchArgs = [...invocationArgs, "--hook-event-id", admission.eventKey];
199
+ preparedInput?.route?.parsed.options.set("hook-event-id", [admission.eventKey]);
162
200
  dispatchContext.env.DD_FLOW_INVOCATION_SCOPE = JSON.stringify(lifecycleInvocationScope(dispatchContext, invocationId));
163
201
  dispatchContext.env.DD_FLOW_CURRENT_INVOCATION = invocationId;
164
202
  }
@@ -167,9 +205,13 @@ export async function runCli(args, io = defaultIo, env = process.env) {
167
205
  const error = retainedOutcome.error;
168
206
  throw new AppError(error.code, error.message, error.exitCode, error.details);
169
207
  }
208
+ if (!retainedOutcome && !preparedInput)
209
+ preparedInput = await prepareCliInput(invocationArgs, dispatchContext, io, scopeProjectRoot, output.responseFile);
210
+ if (!retainedOutcome)
211
+ preflightCliCompatibility(dispatchContext, invocationArgs, env, scopeProjectRoot);
170
212
  if (!retainedOutcome)
171
213
  settleHook = observeLifecycleCommand(dispatchContext, quote(["dd-flow", ...dispatchArgs]));
172
- const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output);
214
+ const result = retainedOutcome ? retainedOutcome.result : await dispatch(dispatchArgs, dispatchContext, io, scopeProjectRoot, classification, output, preparedInput);
173
215
  const completedHook = settleHook;
174
216
  settleHook = undefined;
175
217
  completedHook?.({ result });
@@ -233,13 +275,17 @@ export async function runCli(args, io = defaultIo, env = process.env) {
233
275
  const payload = isAppError(error)
234
276
  ? { ok: false, error: { code: error.code, message: error.message, details: error.details } }
235
277
  : { ok: false, error: { code: "unexpected", message: String(error) } };
278
+ progress.splice(0, progress.length, ...progress.filter(event => event.phase !== "done"));
279
+ if (progress.length)
280
+ progress.push({ phase: "failed", message: payload.error.message, at: new Date().toISOString() });
236
281
  emitHumanProgress(io, output, progress, "failed");
237
282
  if (output.progressJsonl)
238
283
  writeProgressJsonl(io, "failed", payload.error.message, { code: payload.error.code });
239
284
  const errorPayload = attachProgress(payload, progress, output);
240
285
  let visible = errorPayload;
241
286
  try {
242
- visible = writeResponseFile(errorPayload, output);
287
+ if (error.details.parameter !== "response-file")
288
+ visible = writeResponseFile(errorPayload, output);
243
289
  }
244
290
  catch (publication) {
245
291
  error.details.response_publication_error = { code: publication?.code ?? "response_publication_failed", message: publication instanceof Error ? publication.message : String(publication) };
@@ -267,6 +313,20 @@ function resolveProjectRootBeforeRouting(args, env) {
267
313
  }
268
314
  function databaseModeForCommand(args, classification) {
269
315
  const [family, command] = args;
316
+ // Managed attempts are issued only from a prepared store. Opening that
317
+ // existing writer must not run migrations before their input is admitted.
318
+ if (parseArgs(args.slice(2)).options.has("invocation-id"))
319
+ return "hook";
320
+ // Native hook ingress must remain synchronous, but it must not run schema
321
+ // migration/VACUUM while the provider is waiting for its decision. Startup
322
+ // prepares the store; the hook mode only verifies the prepared contract and
323
+ // records the receipt.
324
+ if (family === "codex" && command === "hook" && args[2] === "handle") {
325
+ // Only the participating PreToolUse ingress needs the prepared hot-path
326
+ // contract. SessionStart/Stop/PostToolUse are observational no-ops here
327
+ // and retain the ordinary startup path for backwards compatibility.
328
+ return optionalOption(parseArgs(args.slice(3)), "event") === "PreToolUse" ? "hook" : "initialize";
329
+ }
270
330
  if (family === "engine" ||
271
331
  family === "storage" && command === "migrate-writer" ||
272
332
  family === "version" ||
@@ -324,8 +384,10 @@ function parseOutputOptions(args) {
324
384
  continue;
325
385
  }
326
386
  if (arg === "--response-file") {
387
+ if (responseFile !== null)
388
+ throw new AppError("usage", "--response-file may be supplied only once", 2);
327
389
  const file = args[++index];
328
- if (!file)
390
+ if (!file?.trim() || file.startsWith("--"))
329
391
  throw new AppError("usage", "--response-file requires a path", 2);
330
392
  responseFile = file;
331
393
  continue;
@@ -337,6 +399,33 @@ function parseOutputOptions(args) {
337
399
  const hookJsonOnly = stripped[0] === "codex" && stripped[1] === "hook" && stripped[2] === "handle";
338
400
  return { json: json || hookJsonOnly, progressJsonl, args: stripped, hookJsonOnly, responseFile };
339
401
  }
402
+ /** Validate the response destination without creating it. The response is an
403
+ * optional publication, but a known-bad destination should never be found
404
+ * only after a successful lifecycle mutation. */
405
+ function prepareResponseDestination(value) {
406
+ const file = path.resolve(value);
407
+ const parent = path.dirname(file);
408
+ try {
409
+ let ancestor = parent;
410
+ for (;;) {
411
+ try {
412
+ if (!fs.statSync(ancestor).isDirectory())
413
+ throw new AppError("response_file_parent_invalid", "--response-file parent must be a directory", 2, { file, parent });
414
+ break;
415
+ }
416
+ catch (error) {
417
+ if (error.code !== "ENOENT" || path.dirname(ancestor) === ancestor)
418
+ throw error;
419
+ ancestor = path.dirname(ancestor);
420
+ }
421
+ }
422
+ if (fs.existsSync(file) && !fs.statSync(file).isFile())
423
+ throw new AppError("response_file_invalid", "--response-file must name a regular file", 2, { file });
424
+ }
425
+ catch (error) {
426
+ return prepareError(error, { parameter: "response-file", file, parent });
427
+ }
428
+ }
340
429
  /** Persist the complete machine response for harnesses that cap tool output.
341
430
  * The visible response keeps routing fields but omits the potentially large
342
431
  * rendered prompt; an agent can safely read the named file without rerunning
@@ -617,13 +706,609 @@ function stringValue(value) {
617
706
  function shortSha(value) {
618
707
  return value ? value.slice(0, 12) : "unknown";
619
708
  }
620
- async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args)) {
621
- const [family, command, ...rest] = args;
622
- const parsed = parseArgs(rest);
709
+ /** All caller supplied file/stdin data is consumed here, before a managed
710
+ * invocation is allowed to claim execution or a stage claims its hook event. */
711
+ async function prepareCliInput(...args) {
712
+ try {
713
+ return await prepareCliInputValue(...args);
714
+ }
715
+ catch (error) {
716
+ return prepareError(error);
717
+ }
718
+ }
719
+ async function prepareCliInputValue(args, context, io, scopeProjectRoot, responseFile = null, route = prepareCommandRoute(args)) {
720
+ const { family, command, parsed } = route;
721
+ const prepared = { route };
722
+ if (route.key === "project register")
723
+ resolveProjectRoot(requiredOption(parsed, "root"));
724
+ if (route.key === "project archive")
725
+ prepareProjectArchive(context, { idOrAlias: parsed.positional[0], root: optionalOption(parsed, "root"), reason: requiredOption(parsed, "reason") });
726
+ if (route.key === "protocol register")
727
+ prepareProtocolRegistration(context, { handshakeId: requiredPosition(parsed, 0, "handshake-id"), projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "workspace-path") });
728
+ if (route.key === "canon register")
729
+ prepared.canonRegistration = prepareCanonRegistration(context, { root: requiredOption(parsed, "root") });
730
+ if (route.key === "run attach-stage")
731
+ prepareFlowRunStageAttachment(context, stageAttachmentInput(parsed));
732
+ if (route.key === "run complete-stage")
733
+ prepareFlowRunStageCompletion(context, stageCompletionInput(parsed));
734
+ if (route.key === "run override")
735
+ prepareFlowRunCompletion(context, runOverrideInput(parsed));
736
+ if (route.key === "engine bind-run")
737
+ prepared.engineBind = prepareRunEngineBind(context, engineBindInput(parsed));
738
+ if (route.key === "worktree create")
739
+ 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") });
740
+ if (route.key === "worktree bootstrap")
741
+ prepared.worktreeBootstrap = prepareWorktreeBootstrap(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree bootstrap") });
742
+ if (route.key === "worktree close")
743
+ prepareWorktreeClose(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "worktree close"), mode: requiredOption(parsed, "mode") });
744
+ if (route.key.startsWith("plan item "))
745
+ preparePlanItemMutation(context, planItemInput(parsed, requiredScopeProjectRoot(scopeProjectRoot, route.key)));
746
+ if (route.key === "lane workspace set")
747
+ prepareLaneWorkspace(context, laneWorkspaceInput(parsed));
748
+ if (route.key === "lane waiter cancel")
749
+ prepareLaneWaiterCancellation(context, laneWaiterCancelInput(parsed));
750
+ if (route.key === "merge-queue complete")
751
+ prepareMergeJobCompletion(context, { ...mergeJobTarget(parsed, scopeProjectRoot), summary: requiredOption(parsed, "summary") });
752
+ if (route.key === "merge-queue cancel")
753
+ prepareMergeQueueCancellation(context, mergeCancellationInput(parsed, scopeProjectRoot));
754
+ if (route.key === "merge-queue wait-next")
755
+ prepareMergeQueueWait(context, mergeWaitInput(parsed, scopeProjectRoot));
756
+ if (route.key === "merge-queue next" || route.key === "merge-queue claim")
757
+ 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") } : {}) });
758
+ if (route.key === "merge-queue note")
759
+ prepareMergeJobNote(context, { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue note"), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
760
+ if (route.key === "merge bundle complete")
761
+ prepareMergeBundleCompletion(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), summary: requiredOption(parsed, "summary") });
762
+ if (route.key === "merge bundle claim")
763
+ prepareMergeBundleClaim(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed) });
764
+ if (route.key === "merge bundle fail")
765
+ prepareMergeBundleFailure(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: workspacePathOption(parsed), workerId: requiredWorkerId(parsed), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
766
+ if (route.key === "merge-queue fail")
767
+ prepareMergeJobFailure(context, { ...mergeJobTarget(parsed, scopeProjectRoot), reason: requiredOption(parsed, "reason"), requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue") });
768
+ if (route.key.startsWith("lane lock ") && route.key !== "lane lock status") {
769
+ const action = requiredPosition(parsed, 0, "lane lock action");
770
+ prepareLaneLock(context, {
771
+ projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"),
772
+ workspacePath: workspacePathOption(parsed), leaseToken: optionalOption(parsed, "lease-token"), reason: optionalOption(parsed, "reason"),
773
+ ttlSeconds: optionalNumber(parsed, "ttl"), timeoutSeconds: optionalNumber(parsed, "timeout"), pollIntervalSeconds: optionalNumber(parsed, "poll-interval")
774
+ }, action);
775
+ }
776
+ if (route.key === "run recovery begin")
777
+ prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "interruption-json"), "interruption");
778
+ if (route.key === "run recovery seal")
779
+ prepared.recoveryPayload = prepareRecoveryPayload(requiredOption(parsed, "settlement-json"), "settlement");
780
+ if (route.key === "run recovery capture")
781
+ prepared.recoveryCapture = prepareRecoveryCapture(context, recoveryCaptureInput(parsed));
782
+ if (route.key === "run recovery resume")
783
+ prepared.recoveryResume = prepareRunRecoveryResume(context, recoveryResumeInput(parsed));
784
+ if (route.key === "run drive answer")
785
+ prepared.controllerAnswer = prepareRunControllerAnswer(context, controllerAnswerInput(parsed));
786
+ if (route.key === "run drive launch")
787
+ prepared.controllerLaunch = prepareRunControllerLaunch(context, controllerLaunchInput(parsed));
788
+ if (route.key === "run drive context")
789
+ prepared.controllerContext = prepareRunControllerContext(context, controllerContextInput(parsed));
790
+ if (route.key === "protocol transition" || route.key === "transition")
791
+ prepared.protocolTransition = prepareProtocolTransition(context, protocolTransitionInput(parsed, scopeProjectRoot ?? resolveOperationProjectRoot(context, args)));
792
+ if (route.key === "codex home init")
793
+ prepared.codexHome = prepareCodexHome(context, codexHomeInput(parsed));
794
+ if (route.key === "codex home remove")
795
+ prepareCodexHomeRemoval(context, { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), mode: requiredOption(parsed, "mode") });
796
+ if (["codex hooks install", "codex hooks remove"].includes(route.key))
797
+ prepared.codexHooks = prepareCodexHooks(context, { projectRoot: requiredOption(parsed, "project-root"), target: optionalOption(parsed, "target"), profile: optionalOption(parsed, "profile"), yes: hasOption(parsed, "yes") });
798
+ if (route.key === "migration apply")
799
+ prepared.migrationApply = prepareMigrationApply(context, migrationApplyInput(parsed, classifyCliOperation(args, context.env)));
800
+ if (route.key === "migration verify")
801
+ prepared.migrationVerification = verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
802
+ if (responseFile)
803
+ prepareResponseDestination(responseFile);
804
+ if (family === "dashboard") {
805
+ const action = dashboardAction(command);
806
+ prepareDashboardOptions({ format: optionalOption(parsed, "format"), viewer: optionalOption(parsed, "viewer"), open: optionalOption(parsed, "open") });
807
+ 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") });
808
+ if (action === "refresh" && target.kind !== "global" && optionalOption(parsed, "output") !== undefined) {
809
+ throw new AppError("validation", "--output is supported only for global dashboard refresh; use dashboard render for a custom project/protocol output", 2, { parameter: "output" });
810
+ }
811
+ if (target.kind === "protocol")
812
+ requireProtocol(context, target.protocolId, target.project.id);
813
+ if (action !== "data") {
814
+ if (target.kind === "global" || target.kind === "all_projects") {
815
+ prepareGlobalDashboardRender(context, { output: optionalOption(parsed, "output"), format: optionalOption(parsed, "format") ?? (command?.endsWith("-global") ? undefined : "html") });
816
+ }
817
+ else {
818
+ const format = optionalOption(parsed, "format") ?? (action === "open" || hasOption(parsed, "project") ? "html" : undefined);
819
+ const input = { projectRoot: target.project.root, format, ...(target.kind === "protocol" ? { protocol: target.protocolId } : {}) };
820
+ if (action === "refresh")
821
+ prepareDashboardRefresh(context, { ...input, open: optionalOption(parsed, "open") });
822
+ else if (action === "render")
823
+ prepareDashboardRender(context, { ...input, output: optionalOption(parsed, "output") });
824
+ else if (target.kind === "protocol") {
825
+ if (format !== "html")
826
+ throw new AppError("validation", "Protocol dashboard open supports --format html", 2, { protocol: target.protocolId });
827
+ prepareDashboardRender(context, input);
828
+ }
829
+ }
830
+ }
831
+ }
832
+ if (route.key === "prompt render") {
833
+ const input = workerPromptInput(parsed);
834
+ prepared.workerPrompt = { input, value: prepareWorkerPrompt(context, input) };
835
+ }
836
+ if (route.key === "run snapshot restore" || route.key === "run snapshot bootstrap restore") {
837
+ try {
838
+ const input = snapshotRestoreInput(parsed);
839
+ if (route.key === "run snapshot restore")
840
+ prepared.snapshotRestore = prepareEvalRunRestore(context, input);
841
+ else
842
+ prepared.bootstrapRestore = prepareEvalBootstrapRestore(input);
843
+ }
844
+ catch (error) {
845
+ return prepareError(error, { parameter: "snapshot/project-root" });
846
+ }
847
+ }
848
+ if (route.key === "run snapshot bootstrap create") {
849
+ try {
850
+ prepareEvalBootstrapSnapshot(context, { projectRoot: requiredOption(parsed, "project-root"), output: requiredOption(parsed, "output") });
851
+ }
852
+ catch (error) {
853
+ return prepareError(error, { parameter: "project-root/output" });
854
+ }
855
+ }
856
+ if (route.key === "run snapshot create") {
857
+ prepareEvalRunSnapshotCreation(context, snapshotCreateInput(parsed));
858
+ }
859
+ if (route.key === "run start") {
860
+ try {
861
+ const input = runStartInput(parsed);
862
+ prepared.runStart = { input, value: prepareFlowRunStart(context, input) };
863
+ }
864
+ catch (error) {
865
+ return prepareError(error, { parameter: "run start" });
866
+ }
867
+ }
868
+ if (route.key === "run fork") {
869
+ try {
870
+ prepared.runFork = prepareRunFork(runForkInput(parsed));
871
+ }
872
+ catch (error) {
873
+ return prepareError(error, { parameter: "from/output" });
874
+ }
875
+ }
876
+ if (["runtime scope fence", "runtime scope stop"].includes(route.key))
877
+ prepareRuntimeScopeFence(requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"));
878
+ if (route.key === "runtime scope resume")
879
+ prepareRuntimeScopeResumeRequest(context, runtimeScopeResumeInput(parsed));
880
+ if (route.key === "runtime scope reconcile")
881
+ prepareRuntimeScopeReconciliation(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")), requiredOption(parsed, "request-id"));
882
+ if (route.key === "runtime scope serve")
883
+ prepareRuntimeScopeServing(context, requiredOption(parsed, "scope-id"), Number(requiredOption(parsed, "generation")));
884
+ if (route.key === "runtime scope control") {
885
+ try {
886
+ const raw = optionalOption(parsed, "manifest-json");
887
+ 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);
888
+ }
889
+ catch (error) {
890
+ return prepareError(error, { parameter: "scope-id/request-id/manifest-json" });
891
+ }
892
+ }
893
+ if (route.key === "runtime process register") {
894
+ prepareManagedProcessRegistration(context, runtimeProcessRegistrationInput(context, parsed));
895
+ }
896
+ if (route.key === "runtime process heartbeat")
897
+ prepareManagedProcessHeartbeat(context, runtimeProcessHeartbeatInput(parsed));
898
+ if (route.key === "runtime process finish")
899
+ prepareManagedProcessFinish(context, runtimeProcessFinishInput(parsed));
900
+ if (route.key === "runtime process confirm")
901
+ prepareManagedProcessConfirmation(context, runtimeProcessConfirmationInput(parsed));
902
+ if (route.key === "runtime process stop")
903
+ prepareManagedProcessStopOperation(context, runtimeProcessStopInput(parsed));
904
+ if (route.key === "runtime process reconcile")
905
+ prepareManagedProcessReconcile(requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms"));
906
+ if (route.key === "runtime process release-turn") {
907
+ prepared.providerTurnRelease = prepareProviderTurnRelease({ daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
908
+ }
909
+ if (route.key === "runtime process start") {
910
+ try {
911
+ prepareRuntimeService(context, runtimeServiceInput(parsed));
912
+ }
913
+ catch (error) {
914
+ return prepareError(error, { parameter: "runtime process start" });
915
+ }
916
+ }
917
+ if (family === "work" && command === "retry") {
918
+ try {
919
+ prepareWorkRetry(context, requiredPosition(parsed, 0, "work-id"), requiredOption(parsed, "reason"));
920
+ }
921
+ catch (error) {
922
+ return prepareError(error, { parameter: "work-id/reason" });
923
+ }
924
+ }
925
+ if (family === "work" && (command === "fail" || command === "cancel")) {
926
+ try {
927
+ prepareWorkSettlement(context, requiredPosition(parsed, 0, "work-id"), command === "fail" ? "failed" : "cancelled", requiredOption(parsed, "reason"));
928
+ }
929
+ catch (error) {
930
+ return prepareError(error, { parameter: "work-id/reason" });
931
+ }
932
+ }
933
+ if (family === "work" && (command === "deps" || command === "delete")) {
934
+ try {
935
+ if (command === "delete")
936
+ prepareWorkDelete(context, requiredPosition(parsed, 0, "work-id"));
937
+ else
938
+ prepareWorkDeps(context, { workId: requiredPosition(parsed, 1, "work-id"), action: requiredPosition(parsed, 0, "action"), on: parsed.options.get("on") ?? [] });
939
+ }
940
+ catch (error) {
941
+ return prepareError(error, { parameter: "work-id/on" });
942
+ }
943
+ }
944
+ if (route.key === "cleanup apply") {
945
+ const planFile = requiredOption(parsed, "plan-file");
946
+ try {
947
+ prepared.cleanup = prepareCleanupApply(context, { projectRoot: requiredOption(parsed, "project-root"), planFile, reason: requiredOption(parsed, "reason"), force: hasOption(parsed, "force") });
948
+ }
949
+ catch (error) {
950
+ return prepareError(error, { parameter: "plan-file", file: planFile });
951
+ }
952
+ }
953
+ if (route.key === "stage block" || route.key === "stage unblock") {
954
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
955
+ try {
956
+ if (route.key === "stage block") {
957
+ if (!hasOption(parsed, "summary-stdin"))
958
+ throw new AppError("usage", "stage block requires --summary-stdin", 2);
959
+ prepared.interactionText = await readStdin(io.stdin);
960
+ prepareStageRuntimeBlock(context, { ...input, kind: requiredOption(parsed, "kind"), code: requiredOption(parsed, "code"), summary: prepared.interactionText, retryable: hasOption(parsed, "retryable") });
961
+ }
962
+ else
963
+ prepareStageRuntimeUnblock(context, input);
964
+ }
965
+ catch (error) {
966
+ return prepareError(error, { parameter: "stage/work/summary" });
967
+ }
968
+ }
969
+ if (route.key === "zcode invocation prepare") {
970
+ const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label: "invocation issuance" }).value;
971
+ const payload = value && typeof value === "object" ? value : {};
972
+ try {
973
+ prepared.lifecycleIssuance = prepareLifecycleIssuance(payload.command, payload.scope);
974
+ }
975
+ catch (error) {
976
+ if (error instanceof AppError && ["invocation_command_invalid", "invocation_scope_invalid"].includes(error.code))
977
+ return prepareError(new AppError("usage", error.message, 2, { cause_code: error.code }), { parameter: "stdin" });
978
+ throw error;
979
+ }
980
+ assertLifecycleInvocationCurrent(context, prepared.lifecycleIssuance.scope, prepared.lifecycleIssuance.command);
981
+ }
982
+ if (route.key === "work add-batch") {
983
+ const file = requiredOption(parsed, "file");
984
+ try {
985
+ prepared.workBatch = prepareWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file });
986
+ }
987
+ catch (error) {
988
+ return prepareError(error, { parameter: "file/parent", file });
989
+ }
990
+ }
991
+ if (route.key === "work repair add") {
992
+ if (!hasOption(parsed, "task-stdin"))
993
+ throw new AppError("usage", "work repair add requires --task-stdin", 2);
994
+ if (io.stdin?.isTTY)
995
+ throw new AppError("usage", "--task-stdin requires piped or redirected input", 2);
996
+ prepared.workRepair = prepareCliWorkRepair(context, parsed, await readStdin(io.stdin), scopeProjectRoot);
997
+ }
998
+ if (route.key === "run config set" || route.key === "run vars set") {
999
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), key: requiredOption(parsed, "key"), value: requiredOption(parsed, "value") };
1000
+ try {
1001
+ if (route.key === "run config set")
1002
+ prepareFlowRunConfig(context, { ...input, reason: requiredOption(parsed, "reason") });
1003
+ else
1004
+ prepareFlowRunVariable(context, input);
1005
+ }
1006
+ catch (error) {
1007
+ return prepareError(error, { parameter: "run config/vars" });
1008
+ }
1009
+ }
1010
+ if (route.key === "schema validate") {
1011
+ try {
1012
+ prepared.schemaValidation = dispatchSchema(context, command, parsed);
1013
+ }
1014
+ catch (error) {
1015
+ return prepareError(error, { parameter: "file", file: requiredOption(parsed, "file") });
1016
+ }
1017
+ }
1018
+ if (route.key === "project config set") {
1019
+ try {
1020
+ prepareProjectConfigValue(requiredOption(parsed, "key"), requiredOption(parsed, "value"));
1021
+ }
1022
+ catch (error) {
1023
+ return prepareError(error, { parameter: "key/value" });
1024
+ }
1025
+ }
1026
+ if (family === "session" && command === "register") {
1027
+ try {
1028
+ prepared.sessionPayload = prepareFlowSessionPayload({ payloadBase64: optionalOption(parsed, "payload-base64"), payloadFile: optionalOption(parsed, "payload-file"), payloadJson: optionalOption(parsed, "payload-json") });
1029
+ }
1030
+ catch (error) {
1031
+ return prepareError(error, { parameter: "session payload" });
1032
+ }
1033
+ }
1034
+ const run = (projectRoot, runId) => {
1035
+ try {
1036
+ const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
1037
+ const row = context.db.get("SELECT run_root, workspace_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
1038
+ if (!row)
1039
+ throw new AppError("not_found", "RUN is not registered", 2, { run_id: runId });
1040
+ if (!row.run_root)
1041
+ throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId });
1042
+ return { runRoot: row.run_root, workspaceRoot: row.workspace_root, indexJson: row.index_json };
1043
+ }
1044
+ catch (error) {
1045
+ return prepareError(error, { parameter: "run-id", run_id: runId });
1046
+ }
1047
+ };
1048
+ const json = (file, stdin, label, schema) => {
1049
+ if (stdin) {
1050
+ // The caller awaits this helper; the synchronous facade below is only
1051
+ // used for file inputs. Keep stdin handling in the stage branch.
1052
+ throw new AppError("usage", `${label} stdin preparation must be awaited`, 2);
1053
+ }
1054
+ if (!file)
1055
+ throw new AppError("usage", `${label} input is required`, 2);
1056
+ return prepareJsonSchema(file, label, schema);
1057
+ };
1058
+ if (family === "stage" && command === "start") {
1059
+ const stage = requiredOption(parsed, "stage");
1060
+ if (stage !== "specify" && ["bootstrap", "intake-file", "intake-stdin"].some(option => hasOption(parsed, option)))
1061
+ throw new AppError("usage", "--bootstrap and intake inputs are only supported for SPECIFY", 2);
1062
+ requiredOption(parsed, "project-root");
1063
+ if (!hasOption(parsed, "bootstrap"))
1064
+ requiredPosition(parsed, 0, "run-id");
1065
+ if (hasOption(parsed, "session-id"))
1066
+ throw new AppError("usage", "--session-id is managed by the harness lifecycle adapter and is not accepted", 2);
1067
+ const contextFile = optionalOption(parsed, "context-file"), contextSha256 = optionalOption(parsed, "context-sha256");
1068
+ if (Boolean(contextFile) !== Boolean(contextSha256))
1069
+ throw new AppError("usage", "stage start requires --context-file together with --context-sha256", 2);
1070
+ if ([hasOption(parsed, "intake-file"), hasOption(parsed, "intake-stdin"), Boolean(contextFile)].filter(Boolean).length > 1)
1071
+ throw new AppError("usage", "Use exactly one of --intake-file, --intake-stdin or --context-file", 2);
1072
+ if (contextFile && contextSha256) {
1073
+ try {
1074
+ prepared.stageStart = { externalContext: loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) };
1075
+ }
1076
+ catch (error) {
1077
+ return prepareError(error, { file: contextFile, parameter: "context-file" });
1078
+ }
1079
+ if (!hasOption(parsed, "bootstrap")) {
1080
+ const { runRoot } = run(requiredOption(parsed, "project-root"), requiredPosition(parsed, 0, "run-id"));
1081
+ prepareExternalStageContext({ stageRoot: path.join(runRoot, vnextStageDirectory(stage)), loaded: prepared.stageStart.externalContext });
1082
+ }
1083
+ }
1084
+ if (stage === "specify") {
1085
+ const intakeFile = optionalOption(parsed, "intake-file");
1086
+ if (intakeFile)
1087
+ prepared.stageStart = { intakeMarkdown: prepareTextFile(intakeFile, "SPECIFY intake").text };
1088
+ else if (hasOption(parsed, "intake-stdin"))
1089
+ prepared.stageStart = { intakeMarkdown: await readStdin(io.stdin) };
1090
+ else if (prepared.stageStart?.externalContext)
1091
+ prepared.stageStart.intakeMarkdown = runnerIntake(prepared.stageStart.externalContext);
1092
+ const projectRoot = requiredOption(parsed, "project-root");
1093
+ const bootstrap = hasOption(parsed, "bootstrap");
1094
+ if (isVnextSpecifyFlow(projectRoot) && (bootstrap || isVnextSpecifyRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") }))) {
1095
+ 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") } : {}) });
1096
+ }
1097
+ else if (bootstrap) {
1098
+ if (hasOption(parsed, "intake-stdin") || contextFile)
1099
+ throw new AppError("usage", "Legacy SPECIFY bootstrap supports --intake-file, not stdin or runner context", 2);
1100
+ prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot, stage, bootstrap: { subject: requiredOption(parsed, "subject"), ...(intakeFile ? { intakeFile } : {}) }, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) }, prepared.stageStart?.intakeMarkdown);
1101
+ }
1102
+ }
1103
+ if (stage === "merge" && isVnextMergeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1104
+ prepared.mergeStart = prepareVnextMergeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1105
+ }
1106
+ if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1107
+ prepared.protocolizeStart = prepareVnextProtocolizeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1108
+ }
1109
+ if (stage === "plan" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1110
+ prepared.planStart = prepareVnextPlanStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1111
+ }
1112
+ if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1113
+ prepared.planReviewStart = prepareVnextPlanReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1114
+ }
1115
+ if (stage === "code" && isVnextPlanRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1116
+ prepared.codeStart = prepareVnextCodeStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1117
+ }
1118
+ if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id") })) {
1119
+ prepared.codeReviewStart = prepareVnextCodeReviewStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), ...(prepared.stageStart?.externalContext ? { externalContext: prepared.stageStart.externalContext } : {}) });
1120
+ }
1121
+ if (![prepared.specifyStart, prepared.protocolizeStart, prepared.planStart, prepared.planReviewStart, prepared.codeStart, prepared.codeReviewStart, prepared.mergeStart, prepared.legacyStageStart].some(Boolean)) {
1122
+ if (prepared.stageStart?.externalContext)
1123
+ throw new AppError("usage", "--context-file is currently supported only by vNext flow stages", 2);
1124
+ prepared.legacyStageStart = prepareLegacyStageStart(context, { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}) });
1125
+ }
1126
+ }
1127
+ if (family === "stage" && (command === "pause" || command === "resume")) {
1128
+ requiredPosition(parsed, 0, "run-id");
1129
+ requiredOption(parsed, "project-root");
1130
+ requiredOption(parsed, "stage");
1131
+ requiredOption(parsed, "work");
1132
+ const parameter = command === "pause" ? "question-stdin" : "answer-stdin";
1133
+ if (!hasOption(parsed, parameter))
1134
+ throw new AppError("usage", `stage ${command} requires --${parameter}`, 2);
1135
+ if (io.stdin?.isTTY)
1136
+ throw new AppError("usage", `--${parameter} requires piped or redirected input`, 2);
1137
+ prepared.interactionText = await readStdin(io.stdin);
1138
+ if (!prepared.interactionText.trim())
1139
+ return prepareError(new AppError("validation", `stage ${command} requires non-empty --${parameter}`, 2), { parameter });
1140
+ const input = { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 0, "run-id"), stage: requiredOption(parsed, "stage"), workId: requiredOption(parsed, "work") };
1141
+ try {
1142
+ if (command === "pause")
1143
+ prepareStagePause(context, { ...input, question: prepared.interactionText });
1144
+ else
1145
+ prepared.resumeEvidence = prepareStageResume(context, { ...input, answer: prepared.interactionText }).evidence;
1146
+ }
1147
+ catch (error) {
1148
+ return prepareError(error, { parameter: "stage/work" });
1149
+ }
1150
+ }
1151
+ if (family === "work" && command === "finish") {
1152
+ const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = optionalOption(parsed, "reason");
1153
+ if (Boolean(retryReceiptId) !== Boolean(retryReason))
1154
+ throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
1155
+ const resultFile = optionalOption(parsed, "result-file");
1156
+ const resultStdin = hasOption(parsed, "result-stdin");
1157
+ if (Boolean(resultFile) === resultStdin)
1158
+ throw new AppError("usage", "work finish requires exactly one of --result-file or --result-stdin", 2);
1159
+ prepared.workFinish = { result: resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin) };
1160
+ prepareWorkResult(context, requiredPosition(parsed, 0, "work-id"), prepared.workFinish.result);
1161
+ }
1162
+ if (family === "stage" && command === "fanout" && requiredPosition(parsed, 0, "fanout action") === "reconcile") {
1163
+ prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
1164
+ }
1165
+ if (family === "stage" && command === "native" && requiredPosition(parsed, 0, "native action") === "observe") {
1166
+ prepared.observations = prepareNativeObservations(prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value);
1167
+ }
1168
+ if (family !== "stage" || command !== "finish")
1169
+ return prepared;
1170
+ const projectRoot = requiredOption(parsed, "project-root");
1171
+ const runId = requiredPosition(parsed, 0, "run-id");
1172
+ const stage = requiredOption(parsed, "stage");
1173
+ if (hasOption(parsed, "status"))
1174
+ throw new AppError("usage", "Stage status belongs in the semantic input file", 2);
1175
+ if (["result-file", "semantic-file", "data", "result-stdin"].filter(key => hasOption(parsed, key)).length > 1)
1176
+ throw new AppError("usage", "Use exactly one result input: --result-file, --semantic-file, --data or --result-stdin", 2);
1177
+ if (Boolean(optionalOption(parsed, "retry-check")) !== Boolean(optionalOption(parsed, "reason")))
1178
+ throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
1179
+ const resultStdin = hasOption(parsed, "result-stdin");
1180
+ const semanticFile = optionalOption(parsed, "semantic-file") ?? optionalOption(parsed, "data");
1181
+ const resultFile = optionalOption(parsed, "result-file") ?? semanticFile;
1182
+ const runtime = run(projectRoot, runId);
1183
+ const terminalReplay = (name) => {
1184
+ let index;
1185
+ try {
1186
+ index = JSON.parse(runtime.indexJson);
1187
+ }
1188
+ catch (error) {
1189
+ 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) });
1190
+ }
1191
+ return index.stage_runs?.some(entry => entry.stage === name && entry.status === "done") === true
1192
+ && fs.existsSync(path.join(runtime.runRoot, vnextStageDirectory(name), "stage-report.json"));
1193
+ };
1194
+ const acceptStageInputs = (allowed) => {
1195
+ const options = allowed.split(" ");
1196
+ for (const name of ["dir", "semantic-file", "data", "result-file", "result-stdin", "outcome", "decision-file", "verification-file", "retry-check", "reason", "request", "work"]) {
1197
+ if (hasOption(parsed, name) && !options.includes(name))
1198
+ throw new AppError("usage", `stage finish ${stage} does not accept --${name} for this flow`, 2, { parameter: name });
1199
+ }
1200
+ };
1201
+ const fromStdin = async (label, schema) => {
1202
+ const value = prepareJsonText(await readStdin(io.stdin), { file: "<stdin>", label });
1203
+ try {
1204
+ validateSchema({ ...schema, file: value.file, data: value.value });
1205
+ }
1206
+ catch (error) {
1207
+ return prepareError(error, { file: value.file, label });
1208
+ }
1209
+ return value;
1210
+ };
1211
+ if (stage === "specify" && isVnextSpecifyRun(context, { projectRoot, runId })) {
1212
+ acceptStageInputs("result-file semantic-file data result-stdin outcome");
1213
+ const outcome = optionalOption(parsed, "outcome");
1214
+ if (!outcome || !["specified", "failed", "cancelled"].includes(outcome))
1215
+ throw new AppError("usage", "vNext SPECIFY requires --outcome specified|failed|cancelled; use stage pause for a user question", 2);
1216
+ if (Boolean(resultFile) === resultStdin)
1217
+ throw new AppError("usage", "vNext SPECIFY finish requires exactly one result input", 2);
1218
+ const schema = { schemaName: "vnext-specify", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot };
1219
+ const input = resultStdin ? await fromStdin("SPECIFY result", schema) : json(resultFile, false, "SPECIFY result", schema);
1220
+ prepared.stageFinish = { value: input.value, text: input.text };
1221
+ }
1222
+ else if (stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId })) {
1223
+ acceptStageInputs("result-file semantic-file data");
1224
+ if (!resultFile || resultStdin || hasOption(parsed, "outcome"))
1225
+ throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
1226
+ let absolute;
1227
+ try {
1228
+ absolute = prepareProtocolizeResultPath(runtime.runRoot, resultFile);
1229
+ }
1230
+ catch (error) {
1231
+ return prepareError(error, { file: resultFile });
1232
+ }
1233
+ const input = json(absolute, false, "PROTOCOLIZE result", { schemaName: "vnext-protocolize-result", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1234
+ prepared.stageFinish = { value: input.value, text: input.text };
1235
+ }
1236
+ else if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
1237
+ acceptStageInputs("");
1238
+ if (hasOption(parsed, "code-work-batch-file"))
1239
+ throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
1240
+ try {
1241
+ prepared.planFinish = prepareVnextPlanFinish(context, { projectRoot, runId });
1242
+ }
1243
+ catch (error) {
1244
+ return prepareError(error, { parameter: "PLAN artifacts" });
1245
+ }
1246
+ }
1247
+ else if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
1248
+ acceptStageInputs("request work retry-check reason");
1249
+ if (resultStdin || resultFile || hasOption(parsed, "outcome"))
1250
+ throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
1251
+ requiredOption(parsed, "request");
1252
+ requiredOption(parsed, "work");
1253
+ try {
1254
+ prepared.mergeFinish = prepareVnextMergeFinish(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(optionalOption(parsed, "retry-check") ? { retryCheckId: optionalOption(parsed, "retry-check"), retryReason: optionalOption(parsed, "reason") } : {}) });
1255
+ }
1256
+ catch (error) {
1257
+ return prepareError(error, { parameter: "MERGE finish" });
1258
+ }
1259
+ }
1260
+ else if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
1261
+ acceptStageInputs("decision-file");
1262
+ if (resultStdin || hasOption(parsed, "outcome"))
1263
+ throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
1264
+ const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "04-plan-review", "decision.json");
1265
+ const input = json(decision, false, "PLAN-REVIEW decision", { schemaName: "plan-review-decision", projectRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1266
+ prepared.stageFinish = { value: input.value, text: input.text };
1267
+ }
1268
+ else if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
1269
+ acceptStageInputs("verification-file retry-check reason");
1270
+ if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome"))
1271
+ throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
1272
+ const verification = optionalOption(parsed, "verification-file");
1273
+ if (!verification)
1274
+ throw new AppError("usage", "vNext CODE finish requires --verification-file", 2);
1275
+ if (!terminalReplay(stage)) {
1276
+ const input = json(verification, false, "CODE verification", { schemaName: "code-verification", projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1277
+ prepared.stageFinish = { value: input.value, text: input.text };
1278
+ }
1279
+ }
1280
+ else if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
1281
+ acceptStageInputs("decision-file retry-check reason");
1282
+ if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
1283
+ throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
1284
+ const decision = optionalOption(parsed, "decision-file") ?? path.join(runtime.runRoot, "06-code-review", "decision.json");
1285
+ if (!terminalReplay(stage)) {
1286
+ const input = prepareCodeReviewDecision(runtime.runRoot, optionalOption(parsed, "decision-file"));
1287
+ try {
1288
+ validateSchema({ schemaName: "code-review-decision", file: decision, data: input.value, projectRoot: runtime.workspaceRoot, ddFlowHome: context.ddFlowHome, runId, runRoot: runtime.runRoot });
1289
+ }
1290
+ catch (error) {
1291
+ return prepareError(error, { file: decision, label: "CODE-REVIEW decision" });
1292
+ }
1293
+ prepared.stageFinish = input;
1294
+ }
1295
+ }
1296
+ else {
1297
+ acceptStageInputs("dir semantic-file data");
1298
+ if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin)
1299
+ throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
1300
+ prepared.legacyStageFinish = prepareStageFinish(context, { projectRoot, runId, stage, ...(optionalOption(parsed, "dir") ? { dir: optionalOption(parsed, "dir") } : {}), ...(semanticFile ? { semanticFile } : {}) });
1301
+ }
1302
+ return prepared;
1303
+ }
1304
+ async function dispatch(args, context, io, scopeProjectRoot = null, classification = classifyCliOperation(args, context.env), output = parseOutputOptions(args), prepared = {}) {
1305
+ if (!prepared.route)
1306
+ throw new AppError("command_not_prepared", "Command dispatch requires prepared arguments", 1);
1307
+ const { family, command, parsed } = prepared.route;
623
1308
  if (family === "zcode" && command === "invocation" && parsed.positional[0] === "prepare") {
624
- const payload = JSON.parse(await readStdin(io.stdin));
625
- if (typeof payload.command !== "string" || !payload.scope || typeof payload.scope !== "object")
626
- throw new AppError("usage", "Expected command and managed scope on stdin", 2);
1309
+ const payload = prepared.lifecycleIssuance;
1310
+ if (!payload)
1311
+ throw new AppError("command_not_prepared", "Invocation issuance requires prepared input", 1);
627
1312
  assertLifecycleInvocationCurrent(context, payload.scope, payload.command);
628
1313
  return { ok: true, ...issueLifecycleInvocation(context, payload.command, payload.scope) };
629
1314
  }
@@ -635,23 +1320,22 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
635
1320
  return { ok: true, ...migrateStoreWriter(requiredOption(parsed, "database"), requiredOption(parsed, "backup")) };
636
1321
  }
637
1322
  if (family === "engine") {
638
- return dispatchEngine(context, command, parsed, classification);
1323
+ return dispatchEngine(context, command, parsed, classification, prepared);
639
1324
  }
640
1325
  if (family === "status") {
641
- const rootParsed = parseArgs([command ?? "", ...rest]);
642
1326
  return getRuntimeStatus(context, {
643
- projectRoot: optionalOption(rootParsed, "project-root") ?? optionalOption(rootParsed, "root"),
644
- checkRegistry: hasOption(rootParsed, "check-registry")
1327
+ projectRoot: optionalOption(parsed, "project-root") ?? optionalOption(parsed, "root"),
1328
+ checkRegistry: hasOption(parsed, "check-registry")
645
1329
  });
646
1330
  }
647
1331
  if (family === "version") {
648
1332
  return getCliVersionReport();
649
1333
  }
650
1334
  if (family === "runtime") {
651
- return await dispatchRuntime(context, command, parsed, commandProgress(io, output));
1335
+ return await dispatchRuntime(context, command, parsed, commandProgress(io, output), prepared);
652
1336
  }
653
1337
  if (family === "canon") {
654
- return dispatchCanon(context, command, parsed);
1338
+ return dispatchCanon(context, command, parsed, prepared);
655
1339
  }
656
1340
  if (family === "project" && command === "register") {
657
1341
  return registerProject(context, { root: requiredOption(parsed, "root") });
@@ -728,16 +1412,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
728
1412
  return readyForMerge(context, { protocolId, projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol ready-for-merge") });
729
1413
  }
730
1414
  if (family === "protocol" && command === "transition") {
731
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
732
- const reason = optionalOption(parsed, "reason");
733
- return transitionProtocol(context, {
734
- protocolId,
735
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "protocol transition"),
736
- to: requiredOption(parsed, "to"),
737
- jsonFile: requiredOptionAlias(parsed, ["payload-file", "json-file"]),
738
- force: hasOption(parsed, "force"),
739
- ...(reason ? { reason } : {})
740
- });
1415
+ return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
741
1416
  }
742
1417
  if (family === "protocol" && command === "sync-from-run") {
743
1418
  const protocolId = requiredPosition(parsed, 0, "protocol-id");
@@ -762,17 +1437,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
762
1437
  });
763
1438
  }
764
1439
  if (family === "transition") {
765
- const transitionParsed = parseArgs([command ?? "", ...rest]);
766
- const protocolId = requiredPosition(transitionParsed, 0, "protocol-id");
767
- const reason = optionalOption(transitionParsed, "reason");
768
- return transitionProtocol(context, {
769
- protocolId,
770
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "transition"),
771
- to: requiredOption(transitionParsed, "to"),
772
- jsonFile: requiredOptionAlias(transitionParsed, ["payload-file", "json-file"]),
773
- force: hasOption(transitionParsed, "force"),
774
- ...(reason ? { reason } : {})
775
- });
1440
+ return transitionProtocol(context, protocolTransitionInput(parsed, scopeProjectRoot), prepared.protocolTransition);
776
1441
  }
777
1442
  if (family === "plan" && command === "status") {
778
1443
  const protocolId = requiredPosition(parsed, 0, "protocol-id");
@@ -792,7 +1457,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
792
1457
  if (action === "status")
793
1458
  return getVnextFanoutStatus(context, { projectRoot, runId, stage });
794
1459
  if (action === "reconcile")
795
- return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: JSON.parse(fs.readFileSync(requiredOption(parsed, "observations-file"), "utf8")) });
1460
+ return reconcileVnextFanout(context, { projectRoot, runId, stage, observations: prepared.observations ?? prepareJsonFile(requiredOption(parsed, "observations-file"), "native observations").value });
796
1461
  if (action === "dispatch") {
797
1462
  if (stage === "plan-review")
798
1463
  return dispatchVnextPlanReview(context, { projectRoot, runId });
@@ -803,7 +1468,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
803
1468
  if (family === "stage" && command === "native") {
804
1469
  if (requiredPosition(parsed, 0, "native action") !== "observe")
805
1470
  throw new AppError("usage", "Use: dd-flow stage native observe <RUN> --stage <stage> --observations-file <native-observations.json> --project-root <path>", 2);
806
- return observeVnextNativeChildren(context, { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "stage native observe"), runId: requiredPosition(parsed, 1, "run-id"), stage: requiredOption(parsed, "stage"), observations: JSON.parse(fs.readFileSync(requiredOption(parsed, "observations-file"), "utf8")) });
1471
+ 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 });
807
1472
  }
808
1473
  if (family === "run" && command === "capacity") {
809
1474
  if (requiredPosition(parsed, 0, "capacity action") !== "record")
@@ -815,16 +1480,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
815
1480
  return dispatchPlanItem(context, parsed, requiredScopeProjectRoot(scopeProjectRoot, "plan item"));
816
1481
  }
817
1482
  if (family === "prompt" && command === "render") {
818
- const workspaceRoot = optionalOption(parsed, "workspace-root");
819
- const taskFile = optionalOption(parsed, "task-file");
820
- return renderWorkerPrompt(context, {
821
- projectRoot: requiredOption(parsed, "project-root"),
822
- runId: requiredOption(parsed, "run"),
823
- stage: requiredOption(parsed, "stage"),
824
- profile: requiredOption(parsed, "profile"),
825
- ...(taskFile ? { taskFile } : { planItemId: requiredOption(parsed, "plan-item") }),
826
- ...(workspaceRoot ? { workspaceRoot } : {})
827
- });
1483
+ return renderWorkerPrompt(context, prepared.workerPrompt?.input ?? workerPromptInput(parsed), prepared.workerPrompt?.value);
828
1484
  }
829
1485
  if (family === "work" && command === "status") {
830
1486
  return getVnextWorkStatus(context, {
@@ -833,7 +1489,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
833
1489
  });
834
1490
  }
835
1491
  if (family === "work" && command === "add-batch") {
836
- return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file") });
1492
+ return addWorkBatch(context, { parentWorkId: requiredOption(parsed, "parent"), file: requiredOption(parsed, "file"), prepared: prepared.workBatch });
837
1493
  }
838
1494
  if (family === "work" && command === "ls") {
839
1495
  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")) } : {}) });
@@ -841,48 +1497,9 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
841
1497
  if (family === "work" && command === "show")
842
1498
  return showWork(context, requiredPosition(parsed, 0, "work-id"));
843
1499
  if (family === "work" && command === "repair" && requiredPosition(parsed, 0, "action") === "add") {
844
- if (!hasOption(parsed, "task-stdin"))
845
- throw new AppError("usage", "work repair add requires --task-stdin", 2);
846
- const origins = parsed.options.get("origin-work") ?? [];
847
- const findings = parsed.options.get("from-finding") ?? [];
848
- const unresolved = parsed.options.get("from-unresolved") ?? [];
849
- if (!origins.length && !findings.length)
850
- throw new AppError("usage", "work repair add requires --from-check with --origin-work, --from-finding, or --from-unresolved with --origin-work", 2);
851
- if (findings.length)
852
- return addVnextCodeReviewRepair(context, {
853
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
854
- runId: requiredOption(parsed, "run"),
855
- findingIds: findings,
856
- checkRefsByFinding: Object.fromEntries(findings.map((finding) => [finding, parsed.options.get("check-ref") ?? []])),
857
- objective: await readStdin(io.stdin)
858
- });
859
- if (unresolved.length) {
860
- if (!origins.length)
861
- throw new AppError("usage", "semantic repair requires at least one --origin-work", 2);
862
- const verificationPath = requiredOption(parsed, "verification-file");
863
- return addVnextCodeRepair(context, {
864
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
865
- runId: requiredOption(parsed, "run"),
866
- originWorkIds: origins,
867
- semanticUnresolved: unresolved,
868
- verificationPath,
869
- objective: await readStdin(io.stdin)
870
- });
871
- }
872
- if (!hasOption(parsed, "from-check"))
873
- throw new AppError("usage", "check repair requires --from-check", 2);
874
- if (!origins.length)
875
- throw new AppError("usage", "check repair requires at least one --origin-work", 2);
876
- const receipts = parsed.options.get("from-check") ?? [];
877
- if (receipts.length !== 1)
878
- throw new AppError("usage", "check repair accepts exactly one --from-check; create one causal repair Work per failed receipt", 2);
879
- return addVnextCodeRepair(context, {
880
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
881
- runId: requiredOption(parsed, "run"),
882
- checkReceiptId: receipts[0],
883
- originWorkIds: origins,
884
- objective: await readStdin(io.stdin)
885
- });
1500
+ if (!prepared.workRepair)
1501
+ throw new AppError("cli_input_unprepared", "Repair input was not prepared", 1);
1502
+ return addVnextCodeRepair(context, prepared.workRepair.input, prepared.workRepair.value);
886
1503
  }
887
1504
  if (family === "work" && command === "deps") {
888
1505
  const on = parsed.options.get("on") ?? [];
@@ -909,7 +1526,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
909
1526
  const retryReceiptId = optionalOption(parsed, "retry-check"), retryReason = optionalOption(parsed, "reason");
910
1527
  if (Boolean(retryReceiptId) !== Boolean(retryReason))
911
1528
  throw new AppError("usage", "--retry-check and --reason must be supplied together", 2);
912
- const result = resultFile ? fs.readFileSync(path.resolve(resultFile), "utf8") : await readStdin(io.stdin);
1529
+ const result = prepared.workFinish?.result ?? (resultFile ? prepareTextFile(resultFile, "Work result").text : await readStdin(io.stdin));
913
1530
  return await finishWork(context, workId, result, commandProgress(io, output), retryReceiptId ? { receiptId: retryReceiptId, reason: retryReason } : undefined);
914
1531
  }
915
1532
  if (family === "work" && command === "fail") {
@@ -939,7 +1556,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
939
1556
  const requireSessionBinding = hasOption(parsed, "require-session-binding");
940
1557
  const projectRoot = requiredOption(parsed, "project-root");
941
1558
  const stage = requiredOption(parsed, "stage");
942
- const externalContext = contextFile && contextSha256 ? loadExternalStageContext({ file: contextFile, expectedSha256: contextSha256, stage }) : undefined;
1559
+ const externalContext = prepared.stageStart?.externalContext;
943
1560
  const claimVnextStageStart = (runId) => {
944
1561
  registerProject(context, { root: projectRoot });
945
1562
  return findRecentMatchingHookEvent(context, {
@@ -970,37 +1587,37 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
970
1587
  }
971
1588
  const result = launchVnextSpecify(context, {
972
1589
  projectRoot,
973
- ...(externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
1590
+ ...(prepared.stageStart?.intakeMarkdown !== undefined ? { intakeMarkdown: prepared.stageStart.intakeMarkdown } : externalContext ? { intakeMarkdown: runnerIntake(externalContext) } : intakeFile ? { intakeFile } : { intakeMarkdown: await readStdin(io.stdin) }),
974
1591
  hookEventId,
975
1592
  ...(subject ? { slug: subject } : {}),
976
1593
  ...(externalContext ? { externalContext } : {}),
977
1594
  ...(!bootstrap ? { runId: requiredPosition(parsed, 0, "run-id") } : {})
978
- });
1595
+ }, prepared.specifyStart);
979
1596
  return attachExternalStageContext(result, externalContext);
980
1597
  }
981
1598
  if (!bootstrap && stage === "protocolize" && isVnextProtocolizeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
982
1599
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
983
- return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1600
+ return attachExternalStageContext(startVnextProtocolize(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.protocolizeStart), externalContext);
984
1601
  }
985
1602
  if (!bootstrap && stage === "plan" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
986
1603
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
987
- return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1604
+ return attachExternalStageContext(startVnextPlan(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planStart), externalContext);
988
1605
  }
989
1606
  if (!bootstrap && stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
990
1607
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
991
- return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1608
+ return attachExternalStageContext(startVnextPlanReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.planReviewStart), externalContext);
992
1609
  }
993
1610
  if (!bootstrap && stage === "code" && isVnextPlanRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
994
1611
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
995
- return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1612
+ return attachExternalStageContext(await startVnextCode(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeStart), externalContext);
996
1613
  }
997
1614
  if (!bootstrap && stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
998
1615
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
999
- return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1616
+ return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.codeReviewStart), externalContext);
1000
1617
  }
1001
1618
  if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
1002
1619
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
1003
- return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
1620
+ return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }, prepared.mergeStart), externalContext);
1004
1621
  }
1005
1622
  if (intakeStdin) {
1006
1623
  throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
@@ -1015,7 +1632,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1015
1632
  ...(hookEventId ? { hookEventId } : {}),
1016
1633
  ...(requireSessionBinding ? { requireSessionBinding: true } : {}),
1017
1634
  ...(dir ? { dir } : {})
1018
- });
1635
+ }, prepared.legacyStageStart);
1019
1636
  }
1020
1637
  if (family === "stage" && command === "finish") {
1021
1638
  const dir = optionalOption(parsed, "dir");
@@ -1056,7 +1673,8 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1056
1673
  return controlledReply(await finishVnextSpecifyStage(context, {
1057
1674
  projectRoot,
1058
1675
  runId,
1059
- ...(resultFile ? { resultFile } : { resultJson: await readStdin(io.stdin) }),
1676
+ ...(resultFile ? { resultFile } : prepared.stageFinish?.value ? {} : { resultJson: await readStdin(io.stdin) }),
1677
+ ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}),
1060
1678
  outcome: outcome
1061
1679
  }));
1062
1680
  }
@@ -1064,34 +1682,34 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1064
1682
  if (!resultFile || resultStdin || hasOption(parsed, "outcome")) {
1065
1683
  throw new AppError("usage", "vNext PROTOCOLIZE requires exactly --result-file", 2);
1066
1684
  }
1067
- return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile }));
1685
+ return controlledReply(await finishVnextProtocolize(context, { projectRoot, runId, resultFile, ...(prepared.stageFinish?.value ? { result: prepared.stageFinish.value } : {}) }));
1068
1686
  }
1069
1687
  if (stage === "plan" && isVnextPlanRun(context, { projectRoot, runId })) {
1070
1688
  if (optionalOption(parsed, "code-work-batch-file"))
1071
1689
  throw new AppError("usage", "vNext PLAN derives its CODE batch from accepted semantic plans; do not supply a batch file", 2);
1072
- return controlledReply(await finishVnextPlan(context, { projectRoot, runId }));
1690
+ return controlledReply(await finishVnextPlan(context, { projectRoot, runId, ...(prepared.planFinish ? { prepared: prepared.planFinish } : {}) }));
1073
1691
  }
1074
1692
  if (stage === "plan-review" && isVnextPlanReviewRun(context, { projectRoot, runId })) {
1075
1693
  if (resultStdin || hasOption(parsed, "outcome"))
1076
1694
  throw new AppError("usage", "vNext PLAN-REVIEW uses --decision-file", 2);
1077
1695
  const decisionFile = optionalOption(parsed, "decision-file");
1078
- return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}) }));
1696
+ return controlledReply(await finishVnextPlanReview(context, { projectRoot, runId, ...(decisionFile ? { decisionFile } : {}), ...(prepared.stageFinish?.value ? { decision: prepared.stageFinish.value, decisionText: prepared.stageFinish.text } : {}) }));
1079
1697
  }
1080
1698
  if (stage === "code" && isVnextPlanRun(context, { projectRoot, runId })) {
1081
1699
  if (semanticFile || resultFile || resultStdin || hasOption(parsed, "outcome")) {
1082
1700
  throw new AppError("usage", "vNext CODE finish accepts only --verification-file", 2);
1083
1701
  }
1084
- return controlledReply(await finishVnextCode(context, { projectRoot, runId, verificationFile: optionalOption(parsed, "verification-file") ?? path.join("<missing>", "code-verification.json"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
1702
+ return controlledReply(await finishVnextCode(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) }));
1085
1703
  }
1086
1704
  if (stage === "code-review" && isVnextCodeReviewRun(context, { projectRoot, runId })) {
1087
1705
  if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
1088
1706
  throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
1089
- return controlledReply(await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
1707
+ return controlledReply(await finishVnextCodeReview(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) }));
1090
1708
  }
1091
1709
  if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
1092
1710
  if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
1093
1711
  throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
1094
- return controlledReply(await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }));
1712
+ return controlledReply(await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), ...(retryCheckId ? { retryCheckId, retryReason: retryReason } : {}), progress: commandProgress(io, output) }, prepared.mergeFinish));
1095
1713
  }
1096
1714
  if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
1097
1715
  throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
@@ -1102,7 +1720,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1102
1720
  stage,
1103
1721
  ...(dir ? { dir } : {}),
1104
1722
  ...(semanticFile ? { semanticFile } : {})
1105
- }));
1723
+ }, prepared.legacyStageFinish));
1106
1724
  }
1107
1725
  if (family === "stage" && command === "pause") {
1108
1726
  if (!hasOption(parsed, "question-stdin"))
@@ -1124,7 +1742,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1124
1742
  runId,
1125
1743
  stage,
1126
1744
  workId,
1127
- question: await readStdin(io.stdin)
1745
+ question: prepared.interactionText ?? await readStdin(io.stdin)
1128
1746
  });
1129
1747
  }
1130
1748
  if (family === "stage" && command === "resume") {
@@ -1136,7 +1754,8 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1136
1754
  runId: requiredPosition(parsed, 0, "run-id"),
1137
1755
  stage: requiredOption(parsed, "stage"),
1138
1756
  workId: requiredOption(parsed, "work"),
1139
- answer: await readStdin(io.stdin),
1757
+ answer: prepared.interactionText ?? await readStdin(io.stdin),
1758
+ evidence: prepared.resumeEvidence,
1140
1759
  ...(hookEventId ? { hookEventId } : {})
1141
1760
  });
1142
1761
  }
@@ -1153,7 +1772,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1153
1772
  workId: requiredOption(parsed, "work"),
1154
1773
  kind,
1155
1774
  code: requiredOption(parsed, "code"),
1156
- summary: await readStdin(io.stdin),
1775
+ summary: prepared.interactionText ?? await readStdin(io.stdin),
1157
1776
  retryable: hasOption(parsed, "retryable")
1158
1777
  });
1159
1778
  }
@@ -1178,10 +1797,10 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1178
1797
  return dispatchMergeWorker(context, command, parsed);
1179
1798
  }
1180
1799
  if (family === "cleanup") {
1181
- return dispatchCleanup(context, command, parsed);
1800
+ return dispatchCleanup(context, command, parsed, prepared.cleanup);
1182
1801
  }
1183
1802
  if (family === "session") {
1184
- return dispatchSession(context, command, parsed);
1803
+ return dispatchSession(context, command, parsed, prepared.sessionPayload);
1185
1804
  }
1186
1805
  if (family === "run") {
1187
1806
  if (command === "control") {
@@ -1217,16 +1836,16 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1217
1836
  if (action === "status")
1218
1837
  return runControllerStatus(context, { ...scope, ...(optionalOption(parsed, "after") ? { after: Number(optionalOption(parsed, "after")) } : {}) });
1219
1838
  if (action === "launch")
1220
- return await launchRunController(context, { ...scope, 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") } : {}) });
1839
+ return await launchRunController(context, controllerLaunchInput(parsed), prepared.controllerLaunch);
1221
1840
  if (action === "serve")
1222
1841
  return await serveRunController(context, { ...scope, controllerId: requiredOption(parsed, "controller-id") });
1223
1842
  if (action === "answer")
1224
- return answerRunController(context, { ...scope, requestId: requiredOption(parsed, "request-id"), pauseId: requiredOption(parsed, "pause-id"), answerFile: requiredOption(parsed, "answer-file") });
1843
+ return answerRunController(context, controllerAnswerInput(parsed), prepared.controllerAnswer);
1225
1844
  if (action === "context")
1226
- return supplyRunControllerContext(context, { ...scope, controllerId: requiredOption(parsed, "controller-id"), requestId: requiredOption(parsed, "request-id"), stage: requiredOption(parsed, "stage"), contextFile: requiredOption(parsed, "context-file"), contextSha256: requiredOption(parsed, "context-sha256") });
1845
+ return supplyRunControllerContext(context, controllerContextInput(parsed), prepared.controllerContext);
1227
1846
  throw new AppError("usage", "Use: dd-flow run drive launch|status|answer|context", 2);
1228
1847
  }
1229
- return dispatchRun(context, command, parsed);
1848
+ return dispatchRun(context, command, parsed, prepared);
1230
1849
  }
1231
1850
  if (family === "stat") {
1232
1851
  return dispatchStat(context, command, parsed);
@@ -1241,19 +1860,19 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1241
1860
  return dispatchDashboard(context, command, parsed);
1242
1861
  }
1243
1862
  if (family === "schema") {
1244
- return dispatchSchema(context, command, parsed);
1863
+ return prepared.schemaValidation;
1245
1864
  }
1246
1865
  if (family === "memory") {
1247
1866
  return dispatchMemory(command, parsed);
1248
1867
  }
1249
1868
  if (family === "migration") {
1250
- return dispatchMigration(context, command, parsed, classification);
1869
+ return dispatchMigration(context, command, parsed, classification, prepared);
1251
1870
  }
1252
1871
  if (family === "codex" && command === "hooks") {
1253
- return dispatchCodexHooks(context, parsed);
1872
+ return dispatchCodexHooks(context, parsed, prepared);
1254
1873
  }
1255
1874
  if (family === "codex" && command === "home") {
1256
- return dispatchCodexHome(context, parsed);
1875
+ return dispatchCodexHome(context, parsed, prepared);
1257
1876
  }
1258
1877
  if (family === "codex" && command === "hook") {
1259
1878
  return dispatchCodexHook(context, parsed, await readStdin(io.stdin));
@@ -1303,11 +1922,14 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
1303
1922
  return ingestAgyUsage(context, { projectRoot: requiredOption(parsed, "project-root"), stdin: await readStdin(io.stdin) });
1304
1923
  }
1305
1924
  if (family === "worktree") {
1306
- return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`));
1925
+ return dispatchWorktree(context, command, parsed, requiredScopeProjectRoot(scopeProjectRoot, `worktree ${command ?? ""}`), prepared);
1307
1926
  }
1308
1927
  throw new AppError("usage", `Unknown command: ${args.join(" ") || "<empty>"}`, 2);
1309
1928
  }
1310
- function dispatchEngine(context, command, parsed, classification) {
1929
+ function engineBindInput(parsed) {
1930
+ 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") };
1931
+ }
1932
+ function dispatchEngine(context, command, parsed, classification, prepared) {
1311
1933
  if (command === "install") {
1312
1934
  return installCurrentEngine(context, { force: hasOption(parsed, "force") });
1313
1935
  }
@@ -1332,18 +1954,11 @@ function dispatchEngine(context, command, parsed, classification) {
1332
1954
  });
1333
1955
  }
1334
1956
  if (command === "bind-run") {
1335
- return bindRunEngine(context, {
1336
- projectRoot: requiredOption(parsed, "project-root"),
1337
- runId: requiredPosition(parsed, 0, "run-id"),
1338
- packageName: optionalOption(parsed, "package"),
1339
- engineVersion: requiredOption(parsed, "engine-version"),
1340
- integrityChecksum: optionalOption(parsed, "integrity-checksum"),
1341
- reason: requiredOption(parsed, "reason")
1342
- });
1957
+ return bindRunEngine(context, engineBindInput(parsed), prepared.engineBind);
1343
1958
  }
1344
1959
  throw new AppError("usage", `Unknown engine command: ${command ?? "<empty>"}`, 2);
1345
1960
  }
1346
- function dispatchMigration(context, command, parsed, classification) {
1961
+ function dispatchMigration(context, command, parsed, classification, prepared) {
1347
1962
  if (command === "impact")
1348
1963
  return assessMigrationImpact(context, { projectRoot: requiredOption(parsed, "project-root"), ...(optionalOption(parsed, "target-version") ? { targetVersion: optionalOption(parsed, "target-version") } : {}), ...(optionalOption(parsed, "mode") ? { mode: optionalOption(parsed, "mode") } : {}) });
1349
1964
  if (command === "plan" || command === "report") {
@@ -1359,26 +1974,42 @@ function dispatchMigration(context, command, parsed, classification) {
1359
1974
  });
1360
1975
  }
1361
1976
  if (command === "verify") {
1362
- return verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
1977
+ return prepared.migrationVerification ?? verifyMigrationReport(context, { file: requiredOption(parsed, "file") });
1363
1978
  }
1364
1979
  if (command === "apply") {
1365
- return applyMigration(context, {
1366
- projectRoot: requiredOption(parsed, "project-root"),
1367
- sourceVersion: optionalOption(parsed, "source-version"),
1368
- targetVersion: optionalOption(parsed, "target-version"),
1369
- runId: optionalOption(parsed, "run"),
1370
- backupPath: optionalOption(parsed, "backup-path"),
1371
- backupCreatedAt: optionalOption(parsed, "backup-created-at"),
1372
- reportFile: optionalOption(parsed, "file"),
1373
- allowActive: false,
1374
- classification
1375
- });
1980
+ return applyMigration(context, migrationApplyInput(parsed, classification), prepared.migrationApply);
1376
1981
  }
1377
1982
  throw new AppError("usage", `Unknown migration command: ${command ?? "<empty>"}`, 2);
1378
1983
  }
1379
- function dispatchCanon(context, command, parsed) {
1984
+ function protocolTransitionInput(parsed, scopeProjectRoot) {
1985
+ const reason = optionalOption(parsed, "reason");
1986
+ 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 } : {}) };
1987
+ }
1988
+ function controllerAnswerInput(parsed) {
1989
+ 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") };
1990
+ }
1991
+ function controllerLaunchInput(parsed) {
1992
+ 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") } : {}) };
1993
+ }
1994
+ function controllerContextInput(parsed) {
1995
+ 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") };
1996
+ }
1997
+ function migrationApplyInput(parsed, classification) {
1998
+ return {
1999
+ projectRoot: requiredOption(parsed, "project-root"),
2000
+ sourceVersion: optionalOption(parsed, "source-version"),
2001
+ targetVersion: optionalOption(parsed, "target-version"),
2002
+ runId: optionalOption(parsed, "run"),
2003
+ backupPath: optionalOption(parsed, "backup-path"),
2004
+ backupCreatedAt: optionalOption(parsed, "backup-created-at"),
2005
+ reportFile: optionalOption(parsed, "file"),
2006
+ allowActive: false,
2007
+ classification
2008
+ };
2009
+ }
2010
+ function dispatchCanon(context, command, parsed, prepared) {
1380
2011
  if (command === "register") {
1381
- return registerCanonRoot(context, { root: requiredOption(parsed, "root") });
2012
+ return registerCanonRoot(context, { root: requiredOption(parsed, "root") }, prepared.canonRegistration);
1382
2013
  }
1383
2014
  if (command === "status") {
1384
2015
  return getCanonStatus(context, { root: optionalOption(parsed, "root") });
@@ -1388,6 +2019,15 @@ function dispatchCanon(context, command, parsed) {
1388
2019
  }
1389
2020
  throw new AppError("usage", `Unknown canon command: ${command ?? "<empty>"}`, 2);
1390
2021
  }
2022
+ function mergeJobTarget(parsed, scopeProjectRoot) {
2023
+ return { protocolId: requiredPosition(parsed, 0, "protocol-id"), projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue"), workerId: requiredWorkerId(parsed), workspacePath: workspacePathOption(parsed) };
2024
+ }
2025
+ function mergeCancellationInput(parsed, scopeProjectRoot) {
2026
+ 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") };
2027
+ }
2028
+ function mergeWaitInput(parsed, scopeProjectRoot) {
2029
+ 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 };
2030
+ }
1391
2031
  function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1392
2032
  if (hasOption(parsed, "session-id")) {
1393
2033
  throw new AppError("usage", "--session-id is no longer accepted on merge-queue commands; use --worker-id", 2);
@@ -1411,32 +2051,17 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1411
2051
  });
1412
2052
  }
1413
2053
  if (command === "wait-next") {
1414
- return waitNextMergeJob(context, {
1415
- projectRoot: requiredOptionOrScope(parsed, scopeProjectRoot, "project-root"),
1416
- workerId: requiredWorkerId(parsed),
1417
- timeoutSeconds: optionalNumber(parsed, "timeout"),
1418
- pollIntervalSeconds: optionalNumber(parsed, "poll-interval"),
1419
- workspacePath: workspacePathOption(parsed),
1420
- acquireLock: parseOptionalBoolean(optionalOption(parsed, "acquire-lock"), "acquire-lock") ?? false
1421
- });
2054
+ return waitNextMergeJob(context, mergeWaitInput(parsed, scopeProjectRoot));
1422
2055
  }
1423
2056
  if (command === "complete") {
1424
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1425
2057
  return completeMergeJob(context, {
1426
- protocolId,
1427
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue complete"),
1428
- workerId: requiredWorkerId(parsed),
1429
- workspacePath: workspacePathOption(parsed),
2058
+ ...mergeJobTarget(parsed, scopeProjectRoot),
1430
2059
  summary: requiredOption(parsed, "summary")
1431
2060
  });
1432
2061
  }
1433
2062
  if (command === "fail") {
1434
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1435
2063
  return failMergeJob(context, {
1436
- protocolId,
1437
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue fail"),
1438
- workerId: requiredWorkerId(parsed),
1439
- workspacePath: workspacePathOption(parsed),
2064
+ ...mergeJobTarget(parsed, scopeProjectRoot),
1440
2065
  reason: requiredOption(parsed, "reason"),
1441
2066
  requeue: parseBoolean(requiredOption(parsed, "requeue"), "requeue")
1442
2067
  });
@@ -1451,15 +2076,7 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1451
2076
  });
1452
2077
  }
1453
2078
  if (command === "cancel") {
1454
- const protocolId = requiredPosition(parsed, 0, "protocol-id");
1455
- return cancelMergeQueueJob(context, {
1456
- protocolId,
1457
- projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "merge-queue cancel"),
1458
- reason: requiredOption(parsed, "reason"),
1459
- workerId: optionalOption(parsed, "worker-id"),
1460
- workspacePath: optionalOption(parsed, "path"),
1461
- force: hasOption(parsed, "force")
1462
- });
2079
+ return cancelMergeQueueJob(context, mergeCancellationInput(parsed, scopeProjectRoot));
1463
2080
  }
1464
2081
  throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
1465
2082
  }
@@ -1577,7 +2194,7 @@ function completedCommandExitCode(payload) {
1577
2194
  const exitCode = payload.exit_code;
1578
2195
  return typeof exitCode === "number" && Number.isInteger(exitCode) ? exitCode : 0;
1579
2196
  }
1580
- function dispatchCleanup(context, command, parsed) {
2197
+ function dispatchCleanup(context, command, parsed, prepared) {
1581
2198
  if (command === "scan") {
1582
2199
  return cleanupScan(context, { projectRoot: requiredOption(parsed, "project-root") });
1583
2200
  }
@@ -1585,6 +2202,7 @@ function dispatchCleanup(context, command, parsed) {
1585
2202
  return cleanupApply(context, {
1586
2203
  projectRoot: requiredOption(parsed, "project-root"),
1587
2204
  planFile: requiredOption(parsed, "plan-file"),
2205
+ prepared,
1588
2206
  reason: requiredOption(parsed, "reason"),
1589
2207
  force: hasOption(parsed, "force")
1590
2208
  });
@@ -1605,12 +2223,14 @@ function dispatchProjectConfig(context, parsed) {
1605
2223
  }
1606
2224
  throw new AppError("usage", `Unknown project config action: ${action}`, 2);
1607
2225
  }
1608
- function dispatchSession(context, command, parsed) {
2226
+ function dispatchSession(context, command, parsed, preparedPayload) {
1609
2227
  if (command === "register") {
1610
2228
  return registerFlowSession(context, {
2229
+ ...(preparedPayload ? { preparedPayload } : {}),
1611
2230
  payloadBase64: optionalOption(parsed, "payload-base64"),
1612
2231
  payloadJson: optionalOption(parsed, "payload-json"),
1613
2232
  payloadFile: optionalOption(parsed, "payload-file"),
2233
+ hookEventId: optionalOption(parsed, "hook-event-id"),
1614
2234
  sessionId: optionalOption(parsed, "session-id")
1615
2235
  });
1616
2236
  }
@@ -1643,7 +2263,27 @@ function dispatchSession(context, command, parsed) {
1643
2263
  }
1644
2264
  throw new AppError("usage", `Unknown session command: ${command ?? "<empty>"}`, 2);
1645
2265
  }
1646
- function dispatchRun(context, command, parsed) {
2266
+ function snapshotCreateInput(parsed) {
2267
+ const recoveryId = optionalOption(parsed, "recovery-id"), stageEntry = optionalOption(parsed, "stage-entry");
2268
+ 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 } : {}) };
2269
+ }
2270
+ function snapshotRestoreInput(parsed) {
2271
+ return { snapshot: requiredOption(parsed, "snapshot"), projectRoot: requiredOption(parsed, "project-root"), executionRoutingFile: optionalOption(parsed, "execution-routing-file"), ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}) };
2272
+ }
2273
+ function runStartInput(parsed) {
2274
+ const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
2275
+ 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")) };
2276
+ }
2277
+ function runForkInput(parsed) {
2278
+ 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") } : {}) };
2279
+ }
2280
+ function recoveryCaptureInput(parsed) {
2281
+ return { projectRoot: requiredOption(parsed, "project-root"), runId: requiredPosition(parsed, 1, "run-id"), recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") };
2282
+ }
2283
+ function recoveryResumeInput(parsed) {
2284
+ 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") };
2285
+ }
2286
+ function dispatchRun(context, command, parsed, prepared = {}) {
1647
2287
  if (command === "recovery") {
1648
2288
  const action = requiredPosition(parsed, 0, "recovery action");
1649
2289
  const projectRoot = requiredOption(parsed, "project-root");
@@ -1651,13 +2291,13 @@ function dispatchRun(context, command, parsed) {
1651
2291
  if (action === "inspect")
1652
2292
  return inspectRunRecovery(context, { projectRoot, runId });
1653
2293
  if (action === "begin")
1654
- return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") });
2294
+ return beginRunRecovery(context, { projectRoot, runId, interruption: requiredOption(parsed, "interruption-json") }, prepared.recoveryPayload);
1655
2295
  if (action === "seal")
1656
- return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") });
2296
+ return sealRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), settlement: requiredOption(parsed, "settlement-json") }, prepared.recoveryPayload);
1657
2297
  if (action === "capture")
1658
- return recordRecoveryCapture(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), capturePath: requiredOption(parsed, "capture-path") });
2298
+ return recordRecoveryCapture(context, recoveryCaptureInput(parsed), prepared.recoveryCapture);
1659
2299
  if (action === "resume")
1660
- return resumeRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), daemonId: requiredOption(parsed, "daemon-id"), harness: requiredOption(parsed, "harness"), nativeSessionId: requiredOption(parsed, "native-session-id") });
2300
+ return resumeRunRecovery(context, recoveryResumeInput(parsed), prepared.recoveryResume);
1661
2301
  if (action === "accept")
1662
2302
  return acceptRunRecovery(context, { projectRoot, runId, recoveryId: requiredOption(parsed, "recovery-id"), ...(optionalOption(parsed, "hook-event-id") ? { hookEventId: optionalOption(parsed, "hook-event-id") } : {}) });
1663
2303
  throw new AppError("usage", "Use: dd-flow run recovery inspect|begin|seal|capture|resume|accept", 2);
@@ -1672,49 +2312,18 @@ function dispatchRun(context, command, parsed) {
1672
2312
  output: requiredOption(parsed, "output")
1673
2313
  });
1674
2314
  if (bootstrapAction === "restore")
1675
- return restoreEvalBootstrapSnapshot(context, {
1676
- snapshot: requiredOption(parsed, "snapshot"),
1677
- projectRoot: requiredOption(parsed, "project-root")
1678
- });
2315
+ return restoreEvalBootstrapSnapshot(context, snapshotRestoreInput(parsed), prepared.bootstrapRestore);
1679
2316
  throw new AppError("usage", "Use: dd-flow run snapshot bootstrap create|restore", 2);
1680
2317
  }
1681
2318
  if (action === "create") {
1682
- const candidate = parsed.options.has("candidate");
1683
- const incomplete = parsed.options.has("incomplete");
1684
- const recoveryId = optionalOption(parsed, "recovery-id");
1685
- const stageEntry = optionalOption(parsed, "stage-entry");
1686
- if (Number(candidate) + Number(incomplete) + Number(stageEntry !== undefined) + Number(recoveryId !== undefined) !== 1)
1687
- throw new AppError("usage", "Use exactly one of --candidate, --incomplete, --recovery-id or --stage-entry", 2);
1688
- const input = {
1689
- projectRoot: requiredOption(parsed, "project-root"),
1690
- runId: requiredPosition(parsed, 1, "run-id"),
1691
- output: requiredOption(parsed, "output")
1692
- };
1693
- if (incomplete)
1694
- return createEvalRunSnapshot(context, { ...input, incomplete: true });
1695
- if (recoveryId)
1696
- return createEvalRunSnapshot(context, { ...input, recoveryId });
1697
- return candidate
1698
- ? createEvalRunSnapshot(context, { ...input, candidate: true })
1699
- : createEvalRunSnapshot(context, { ...input, stageEntry: stageEntry });
2319
+ return createEvalRunSnapshot(context, snapshotCreateInput(parsed));
1700
2320
  }
1701
2321
  if (action === "restore")
1702
- return restoreEvalRunSnapshot(context, {
1703
- snapshot: requiredOption(parsed, "snapshot"),
1704
- executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
1705
- ...(optionalOption(parsed, "recovery-id") ? { recoveryId: requiredOption(parsed, "recovery-id") } : {}),
1706
- projectRoot: requiredOption(parsed, "project-root")
1707
- });
2322
+ return restoreEvalRunSnapshot(context, snapshotRestoreInput(parsed), prepared.snapshotRestore);
1708
2323
  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);
1709
2324
  }
1710
2325
  if (command === "fork") {
1711
- return forkRunFromSnapshot(context, {
1712
- snapshot: requiredOption(parsed, "from"),
1713
- output: requiredOption(parsed, "output"),
1714
- engineVersion: requiredOption(parsed, "engine-version"),
1715
- requestId: requiredOption(parsed, "request-id"),
1716
- ...(optionalOption(parsed, "integrity-checksum") ? { integrityChecksum: optionalOption(parsed, "integrity-checksum") } : {})
1717
- });
2326
+ return forkRunFromSnapshot(context, runForkInput(parsed), prepared.runFork);
1718
2327
  }
1719
2328
  if (command === "prepare-vnext-specify") {
1720
2329
  return prepareVnextSpecifyRun(context, { projectRoot: requiredOption(parsed, "project-root"), slug: requiredOption(parsed, "slug"), executionRoutingFile: optionalOption(parsed, "execution-routing-file") });
@@ -1723,21 +2332,7 @@ function dispatchRun(context, command, parsed) {
1723
2332
  if (optionalOption(parsed, "flow-kind") === "mb-upgrade") {
1724
2333
  assertUpgradeRunStorageReady(context, requiredOption(parsed, "project-root"));
1725
2334
  }
1726
- const protocolOverrides = readOptionalJsonFile(optionalOption(parsed, "protocol-override-file"));
1727
- return startFlowRun(context, {
1728
- projectRoot: requiredOption(parsed, "project-root"),
1729
- workspaceRoot: optionalOption(parsed, "workspace-root"),
1730
- flowKind: requiredOption(parsed, "flow-kind"),
1731
- subjectType: requiredOption(parsed, "subject-type"),
1732
- subjectId: requiredOption(parsed, "subject-id"),
1733
- slug: requiredOption(parsed, "slug"),
1734
- nextAction: optionalOption(parsed, "next-action"),
1735
- preset: optionalOption(parsed, "preset"),
1736
- taskProfile: readOptionalJsonFile(optionalOption(parsed, "task-profile-file")),
1737
- executionRoutingFile: optionalOption(parsed, "execution-routing-file"),
1738
- ...(protocolOverrides ? { protocolOverrides } : {}),
1739
- runOverrides: parseFlowFlagOverrides(allOptions(parsed, "flag"))
1740
- });
2335
+ return startFlowRun(context, prepared.runStart?.input ?? runStartInput(parsed), prepared.runStart?.value);
1741
2336
  }
1742
2337
  if (command === "status") {
1743
2338
  return getFlowRunStatus(context, {
@@ -1807,49 +2402,70 @@ function dispatchRun(context, command, parsed) {
1807
2402
  throw new AppError("usage", `Unknown run flags command: ${action}`, 2);
1808
2403
  }
1809
2404
  if (command === "attach-stage") {
1810
- return attachFlowRunStage(context, {
1811
- projectRoot: requiredOption(parsed, "project-root"),
1812
- runId: requiredPosition(parsed, 0, "run-id"),
1813
- stage: requiredOption(parsed, "stage"),
1814
- dir: requiredOption(parsed, "dir"),
1815
- status: requiredOption(parsed, "status"),
1816
- dataSchemaId: optionalOption(parsed, "data-schema-id")
1817
- });
2405
+ return attachFlowRunStage(context, stageAttachmentInput(parsed));
1818
2406
  }
1819
2407
  if (command === "complete-stage") {
1820
- const stageReport = optionalOption(parsed, "stage-report") ?? optionalOption(parsed, "dashboard");
1821
- return completeFlowRunStage(context, {
1822
- projectRoot: requiredOption(parsed, "project-root"),
1823
- runId: requiredPosition(parsed, 0, "run-id"),
1824
- stage: requiredOption(parsed, "stage"),
1825
- status: requiredOption(parsed, "status"),
1826
- stageReport,
1827
- data: optionalOption(parsed, "data"),
1828
- dataSchemaId: optionalOption(parsed, "data-schema-id"),
1829
- report: optionalOption(parsed, "report"),
1830
- aliases: allOptions(parsed, "alias")
1831
- });
2408
+ return completeFlowRunStage(context, stageCompletionInput(parsed));
1832
2409
  }
1833
2410
  if (command === "complete") {
1834
2411
  throw new AppError("usage", "run complete is retired; only an operator may use run override", 2);
1835
2412
  }
1836
2413
  if (command === "override") {
1837
- const status = requiredOption(parsed, "status");
1838
- if (!['cancelled', 'failed'].includes(status)) {
1839
- throw new AppError("usage", "run override status must be cancelled or failed", 2, { status });
1840
- }
1841
- return completeFlowRun(context, {
1842
- projectRoot: requiredOption(parsed, "project-root"),
1843
- runId: requiredPosition(parsed, 0, "run-id"),
1844
- status,
1845
- verdict: optionalOption(parsed, "verdict"),
1846
- nextAction: optionalOption(parsed, "next-action"),
1847
- manualOverrideReason: requiredOption(parsed, "reason")
1848
- });
2414
+ return completeFlowRun(context, runOverrideInput(parsed));
1849
2415
  }
1850
2416
  throw new AppError("usage", `Unknown run command: ${command ?? "<empty>"}`, 2);
1851
2417
  }
1852
- async function dispatchRuntime(context, command, parsed, progress) {
2418
+ function stageAttachmentInput(parsed) {
2419
+ 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") };
2420
+ }
2421
+ function runOverrideInput(parsed) {
2422
+ 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") };
2423
+ }
2424
+ function stageCompletionInput(parsed) {
2425
+ 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") };
2426
+ }
2427
+ function runtimeServiceInput(parsed) {
2428
+ 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 };
2429
+ }
2430
+ function runtimeProcessStopInput(parsed) {
2431
+ const graceMs = optionalNumber(parsed, "grace-ms");
2432
+ return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(graceMs !== undefined ? { graceMs } : {}) };
2433
+ }
2434
+ function runtimeScopeResumeInput(parsed) {
2435
+ return { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") };
2436
+ }
2437
+ function runtimeProcessRegistrationInput(context, parsed) {
2438
+ const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
2439
+ const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
2440
+ const kind = requiredOption(parsed, "kind");
2441
+ const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
2442
+ const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
2443
+ const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
2444
+ return {
2445
+ kind, ownerId: requiredOption(parsed, "owner"), uniqueActiveOperation: kind === "eval-observer",
2446
+ ...(kind === "eval-observer" ? { pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(optionalPositiveNumber(parsed, "process-group-id", true) ? { processGroupId: optionalPositiveNumber(parsed, "process-group-id", true) } : {}) } : {}),
2447
+ ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), projectId, runId,
2448
+ workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
2449
+ operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
2450
+ stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs !== undefined ? { leaseMs } : {}),
2451
+ metadata: { ...(budget ? { budget, harness: optionalOption(parsed, "harness") ?? null } : {}), ...(projectId && runId ? { run_generation: guard?.generation ?? 0, run_admitted: Boolean(run) && !["draining", "sealed", "resuming"].includes(guard?.status ?? "") } : {}), ...(optionalOption(parsed, "dispatch-barrier") ? { dispatch_barrier: optionalOption(parsed, "dispatch-barrier") } : {}), ...(optionalOption(parsed, "role") ? { role: optionalOption(parsed, "role") } : {}), ...(optionalOption(parsed, "state-dir") ? { state_dir: optionalOption(parsed, "state-dir") } : {}), ...(optionalOption(parsed, "adapter-executable") ? { adapter_executable: optionalOption(parsed, "adapter-executable") } : {}) }
2452
+ };
2453
+ }
2454
+ function runtimeProcessConfirmationInput(parsed) {
2455
+ const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true), processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
2456
+ return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ownerPid: optionalPositiveNumber(parsed, "owner-pid", true), pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(processGroupId !== undefined ? { processGroupId } : {}), ...(leaseMs !== undefined ? { leaseMs } : {}) };
2457
+ }
2458
+ function runtimeProcessHeartbeatInput(parsed) {
2459
+ const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
2460
+ return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs !== undefined ? { leaseMs } : {}) };
2461
+ }
2462
+ function runtimeProcessFinishInput(parsed) {
2463
+ const state = requiredOption(parsed, "state");
2464
+ if (state !== "stopped" && state !== "failed")
2465
+ throw new AppError("validation", "--state must be stopped or failed", 2);
2466
+ return { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state, reason: optionalOption(parsed, "reason") ?? null };
2467
+ }
2468
+ async function dispatchRuntime(context, command, parsed, progress, prepared = {}) {
1853
2469
  if (command === "scope") {
1854
2470
  if (parsed.positional.length === 1 && parsed.positional[0] === "reconcile") {
1855
2471
  if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
@@ -1859,7 +2475,7 @@ async function dispatchRuntime(context, command, parsed, progress) {
1859
2475
  if (parsed.positional.length === 1 && parsed.positional[0] === "resume") {
1860
2476
  if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "generation", "capture-key"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
1861
2477
  throw new AppError("usage", "Use runtime scope resume --scope-id ID --request-id ID --generation N --capture-key SHA256", 2);
1862
- return { ok: true, ...await launchRuntimeScopeResume(context, { scopeId: requiredOption(parsed, "scope-id"), requestId: requiredOption(parsed, "request-id"), generation: Number(requiredOption(parsed, "generation")), captureKey: requiredOption(parsed, "capture-key") }) };
2478
+ return { ok: true, ...await launchRuntimeScopeResume(context, runtimeScopeResumeInput(parsed)) };
1863
2479
  }
1864
2480
  if (parsed.positional.length === 1 && parsed.positional[0] === "control") {
1865
2481
  if ([...parsed.options.keys()].some(key => !["scope-id", "request-id", "mode", "manifest-json"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
@@ -1867,7 +2483,7 @@ async function dispatchRuntime(context, command, parsed, progress) {
1867
2483
  const mode = requiredOption(parsed, "mode");
1868
2484
  if (mode !== "pause" && mode !== "stop")
1869
2485
  throw new AppError("usage", "Scope control mode must be pause or stop", 2);
1870
- return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, hasOption(parsed, "manifest-json") ? JSON.parse(requiredOption(parsed, "manifest-json")) : undefined) };
2486
+ return { ok: true, ...await launchRuntimeScopeControl(context, requiredOption(parsed, "scope-id"), requiredOption(parsed, "request-id"), mode, undefined, undefined, undefined, prepared.runtimeManifest) };
1871
2487
  }
1872
2488
  if (parsed.positional.length === 1 && parsed.positional[0] === "serve") {
1873
2489
  if ([...parsed.options.keys()].some(key => !["scope-id", "generation"].includes(key)) || [...parsed.options.values()].some(values => values.length !== 1))
@@ -1894,37 +2510,17 @@ async function dispatchRuntime(context, command, parsed, progress) {
1894
2510
  if (action === "check-admission")
1895
2511
  return { ok: true, ...admitRuntimeCheck(context, requiredOption(parsed, "id"), requiredOption(parsed, "lease-token")) };
1896
2512
  if (action === "start")
1897
- 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 });
2513
+ return await startRuntimeService(context, { ...runtimeServiceInput(parsed), progress });
1898
2514
  if (action === "stop")
1899
- 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) } : {}) }) };
2515
+ return { ok: true, process: await stopManagedProcess(context, runtimeProcessStopInput(parsed)) };
1900
2516
  if (action === "register") {
1901
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1902
- const budget = parseRuntimeBudget(optionalOption(parsed, "budget-json"));
1903
- const projectId = optionalOption(parsed, "project-id") ?? null, runId = optionalOption(parsed, "run") ?? null;
1904
- const guard = projectId && runId ? recoveryGuard(context, projectId, runId) : null;
1905
- const run = projectId && runId ? context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]) : null;
1906
- const process = registerManagedProcess(context, {
1907
- kind: requiredOption(parsed, "kind"), ownerId: requiredOption(parsed, "owner"),
1908
- uniqueActiveOperation: requiredOption(parsed, "kind") === "eval-observer",
1909
- ...(requiredOption(parsed, "kind") === "eval-observer" ? { pid: optionalPositiveNumber(parsed, "pid", true) ?? 0, ...(optionalPositiveNumber(parsed, "process-group-id", true) ? { processGroupId: optionalPositiveNumber(parsed, "process-group-id", true) } : {}) } : {}),
1910
- ownerPid: optionalPositiveNumber(parsed, "owner-pid", true),
1911
- projectId, runId,
1912
- workId: optionalOption(parsed, "work") ?? null, checkId: optionalOption(parsed, "check") ?? null,
1913
- operationId: optionalOption(parsed, "operation") ?? null, stdoutPath: optionalOption(parsed, "stdout") ?? null,
1914
- stderrPath: optionalOption(parsed, "stderr") ?? null, ...(leaseMs ? { leaseMs } : {}),
1915
- metadata: { ...(budget ? { budget, harness: optionalOption(parsed, "harness") ?? null } : {}), ...(projectId && runId ? { run_generation: guard?.generation ?? 0, run_admitted: Boolean(run) && !["draining", "sealed", "resuming"].includes(guard?.status ?? "") } : {}), ...(optionalOption(parsed, "dispatch-barrier") ? { dispatch_barrier: optionalOption(parsed, "dispatch-barrier") } : {}), ...(optionalOption(parsed, "role") ? { role: optionalOption(parsed, "role") } : {}), ...(optionalOption(parsed, "state-dir") ? { state_dir: optionalOption(parsed, "state-dir") } : {}), ...(optionalOption(parsed, "adapter-executable") ? { adapter_executable: optionalOption(parsed, "adapter-executable") } : {}) }
1916
- });
2517
+ const process = registerManagedProcess(context, runtimeProcessRegistrationInput(context, parsed));
1917
2518
  return { ok: true, process };
1918
2519
  }
1919
- if (action === "confirm") {
1920
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1921
- const processGroupId = optionalPositiveNumber(parsed, "process-group-id", true);
1922
- 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 } : {}) }) };
1923
- }
1924
- if (action === "heartbeat") {
1925
- const leaseMs = optionalPositiveNumber(parsed, "lease-ms", true);
1926
- return { ok: heartbeatManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), ...(leaseMs ? { leaseMs } : {}) }) };
1927
- }
2520
+ if (action === "confirm")
2521
+ return { ok: true, process: confirmManagedProcess(context, runtimeProcessConfirmationInput(parsed)) };
2522
+ if (action === "heartbeat")
2523
+ return { ok: heartbeatManagedProcess(context, runtimeProcessHeartbeatInput(parsed)) };
1928
2524
  if (action === "admission") {
1929
2525
  const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
1930
2526
  if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || record.state !== "running" || !record.pid || !record.pid_started_at || !processIsAlive(record))
@@ -1954,18 +2550,14 @@ async function dispatchRuntime(context, command, parsed, progress) {
1954
2550
  const record = managedProcessStatus(context).find(process => process.id === requiredOption(parsed, "id"));
1955
2551
  if (!record || record.lease_token !== requiredOption(parsed, "lease-token") || record.owner_id !== requiredOption(parsed, "owner") || managedProcessRuntimeHome(record, context) !== context.ddFlowHome)
1956
2552
  throw new AppError("process_ownership_unknown", "Budget release requires its retained physical owner", 1);
1957
- return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") });
1958
- }
1959
- if (action === "finish") {
1960
- const state = requiredOption(parsed, "state");
1961
- if (state !== "stopped" && state !== "failed")
1962
- throw new AppError("validation", "--state must be stopped or failed", 2);
1963
- return { ok: finishManagedProcess(context, { id: requiredOption(parsed, "id"), leaseToken: requiredOption(parsed, "lease-token"), state: state, reason: optionalOption(parsed, "reason") ?? null }) };
2553
+ return releaseProviderTurns(context, record, { daemonId: requiredOption(parsed, "daemon-id"), operationId: optionalOption(parsed, "operation-id"), observationFile: optionalOption(parsed, "observation-file") }, prepared.providerTurnRelease);
1964
2554
  }
2555
+ if (action === "finish")
2556
+ return { ok: finishManagedProcess(context, runtimeProcessFinishInput(parsed)) };
1965
2557
  if (action === "status")
1966
2558
  return { ok: true, processes: managedProcessStatus(context) };
1967
2559
  if (action === "reconcile")
1968
- return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalPositiveNumber(parsed, "grace-ms", true)) };
2560
+ return { ok: true, processes: await reconcileExpiredManagedProcesses(context, requiredOption(parsed, "owner"), optionalNumber(parsed, "grace-ms")) };
1969
2561
  throw new AppError("usage", "Usage: dd-flow runtime process <start|stop|register|confirm|heartbeat|admission|finish|status|reconcile>", 2);
1970
2562
  }
1971
2563
  function dispatchStat(context, command, parsed) {
@@ -2153,7 +2745,9 @@ function dispatchSchema(context, command, parsed) {
2153
2745
  // searching project directories: run_id is a structural field in the
2154
2746
  // submitted artifact and the database owns its materialized run_root.
2155
2747
  const explicitRunId = optionalOption(parsed, "run");
2156
- const declaredRunId = explicitRunId ?? readRunId(file);
2748
+ const data = prepareJsonFile(file, "schema input").value;
2749
+ const declared = data && typeof data === "object" && "run_id" in data ? data.run_id : undefined;
2750
+ const declaredRunId = explicitRunId ?? (typeof declared === "string" && declared.trim() ? declared : undefined);
2157
2751
  const fileRunRoot = path.basename(file) === "run.json" ? path.dirname(path.resolve(file)) : undefined;
2158
2752
  const bound = projectRoot
2159
2753
  ? 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 ?? ""])
@@ -2161,6 +2755,7 @@ function dispatchSchema(context, command, parsed) {
2161
2755
  return validateSchema({
2162
2756
  schemaName: requiredOption(parsed, "schema"),
2163
2757
  file,
2758
+ data,
2164
2759
  ...(schemaDir ? { schemaDir } : {}),
2165
2760
  ...(projectRoot ? { projectRoot } : {}),
2166
2761
  ...(declaredRunId || bound?.id ? { runId: declaredRunId ?? bound.id } : {}),
@@ -2170,14 +2765,11 @@ function dispatchSchema(context, command, parsed) {
2170
2765
  }
2171
2766
  throw new AppError("usage", `Unknown schema command: ${command ?? "<empty>"}`, 2);
2172
2767
  }
2173
- function readRunId(file) {
2174
- try {
2175
- const value = JSON.parse(fs.readFileSync(file, "utf8"));
2176
- return typeof value.run_id === "string" && value.run_id.trim() ? value.run_id : undefined;
2177
- }
2178
- catch {
2179
- return undefined;
2180
- }
2768
+ function laneWorkspaceInput(parsed) {
2769
+ return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workspacePath: requiredOption(parsed, "path"), branch: optionalOption(parsed, "branch") };
2770
+ }
2771
+ function laneWaiterCancelInput(parsed) {
2772
+ return { projectRoot: requiredOption(parsed, "project-root"), lane: requiredOption(parsed, "lane"), workerId: requiredOption(parsed, "worker-id"), reason: requiredOption(parsed, "reason") };
2181
2773
  }
2182
2774
  async function dispatchLane(context, command, parsed) {
2183
2775
  if (command === "status") {
@@ -2189,24 +2781,14 @@ async function dispatchLane(context, command, parsed) {
2189
2781
  if (command === "waiter") {
2190
2782
  const action = requiredPosition(parsed, 0, "lane waiter action");
2191
2783
  if (action === "cancel") {
2192
- return cancelLaneWaiter(context, {
2193
- projectRoot: requiredOption(parsed, "project-root"),
2194
- lane: requiredOption(parsed, "lane"),
2195
- workerId: requiredOption(parsed, "worker-id"),
2196
- reason: requiredOption(parsed, "reason")
2197
- });
2784
+ return cancelLaneWaiter(context, laneWaiterCancelInput(parsed));
2198
2785
  }
2199
2786
  throw new AppError("usage", `Unknown lane waiter action: ${action}`, 2);
2200
2787
  }
2201
2788
  if (command === "workspace") {
2202
2789
  const action = requiredPosition(parsed, 0, "lane workspace action");
2203
2790
  if (action === "set") {
2204
- return setLaneWorkspace(context, {
2205
- projectRoot: requiredOption(parsed, "project-root"),
2206
- lane: requiredOption(parsed, "lane"),
2207
- workspacePath: requiredOption(parsed, "path"),
2208
- branch: optionalOption(parsed, "branch")
2209
- });
2791
+ return setLaneWorkspace(context, laneWorkspaceInput(parsed));
2210
2792
  }
2211
2793
  if (action === "check") {
2212
2794
  return checkLaneWorkspace(context, {
@@ -2278,7 +2860,7 @@ async function dispatchLane(context, command, parsed) {
2278
2860
  }
2279
2861
  throw new AppError("usage", `Unknown lane command: ${command ?? "<empty>"}`, 2);
2280
2862
  }
2281
- function dispatchCodexHooks(context, parsed) {
2863
+ function dispatchCodexHooks(context, parsed, prepared) {
2282
2864
  const action = requiredPosition(parsed, 0, "codex hooks action");
2283
2865
  if (action === "print") {
2284
2866
  return printCodexHooks(context, {
@@ -2300,7 +2882,7 @@ function dispatchCodexHooks(context, parsed) {
2300
2882
  target: optionalOption(parsed, "target"),
2301
2883
  profile: optionalOption(parsed, "profile"),
2302
2884
  yes: hasOption(parsed, "yes")
2303
- });
2885
+ }, prepared.codexHooks);
2304
2886
  }
2305
2887
  if (action === "remove") {
2306
2888
  return removeCodexHooks(context, {
@@ -2308,11 +2890,14 @@ function dispatchCodexHooks(context, parsed) {
2308
2890
  target: optionalOption(parsed, "target"),
2309
2891
  profile: optionalOption(parsed, "profile"),
2310
2892
  yes: hasOption(parsed, "yes")
2311
- });
2893
+ }, prepared.codexHooks);
2312
2894
  }
2313
2895
  throw new AppError("usage", `Unknown codex hooks action: ${action}`, 2);
2314
2896
  }
2315
- function dispatchCodexHome(context, parsed) {
2897
+ function codexHomeInput(parsed) {
2898
+ return { projectRoot: requiredOption(parsed, "project-root"), profile: optionalOption(parsed, "profile"), sourceHome: optionalOption(parsed, "source-home"), targetHome: optionalOption(parsed, "target-home") };
2899
+ }
2900
+ function dispatchCodexHome(context, parsed, prepared) {
2316
2901
  const action = requiredPosition(parsed, 0, "codex home action");
2317
2902
  if (action === "plan") {
2318
2903
  return planCodexHome(context, {
@@ -2323,12 +2908,7 @@ function dispatchCodexHome(context, parsed) {
2323
2908
  });
2324
2909
  }
2325
2910
  if (action === "init") {
2326
- return initCodexHome(context, {
2327
- projectRoot: requiredOption(parsed, "project-root"),
2328
- profile: optionalOption(parsed, "profile"),
2329
- sourceHome: optionalOption(parsed, "source-home"),
2330
- targetHome: optionalOption(parsed, "target-home")
2331
- });
2911
+ return initCodexHome(context, codexHomeInput(parsed), prepared.codexHome);
2332
2912
  }
2333
2913
  if (action === "status") {
2334
2914
  return getCodexHomeStatus(context, {
@@ -2351,11 +2931,14 @@ function dispatchCodexHome(context, parsed) {
2351
2931
  }
2352
2932
  throw new AppError("usage", `Unknown codex home action: ${action}`, 2);
2353
2933
  }
2354
- function dispatchCodexHook(context, parsed, stdin) {
2934
+ async function dispatchCodexHook(context, parsed, stdin) {
2355
2935
  const action = requiredPosition(parsed, 0, "codex hook action");
2356
2936
  if (action === "handle") {
2357
2937
  const isolatedHome = isolatedDdFlowHomeFromHookStdin(stdin);
2358
- const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }) : context;
2938
+ const hookContext = isolatedHome ? createContext({ ...context.env, DD_FLOW_HOME: isolatedHome }, "hook") : context;
2939
+ const proof = await verifyCodexHookDelivery(hookContext.env, JSON.parse(stdin || "{}"));
2940
+ if (proof)
2941
+ hookContext.codexHookProof = proof;
2359
2942
  const result = handleCodexHook(hookContext, {
2360
2943
  event: requiredOption(parsed, "event"),
2361
2944
  projectRoot: optionalOption(parsed, "project-root"),
@@ -2410,7 +2993,7 @@ function dispatchAgyEvent(context, parsed, stdin) {
2410
2993
  return handleAgyEvent(context, { projectRoot: requiredOption(parsed, "project-root"), stdin });
2411
2994
  throw new AppError("usage", `Unknown agy event action: ${action}`, 2);
2412
2995
  }
2413
- function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
2996
+ function dispatchWorktree(context, command, parsed, scopeProjectRoot, prepared) {
2414
2997
  if (command === "plan") {
2415
2998
  return planWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
2416
2999
  }
@@ -2421,13 +3004,13 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
2421
3004
  branch: requiredOption(parsed, "branch"),
2422
3005
  base: requiredOption(parsed, "base"),
2423
3006
  path: requiredOption(parsed, "path")
2424
- });
3007
+ }, prepared.worktreeCreate);
2425
3008
  }
2426
3009
  if (command === "status") {
2427
3010
  return getWorktreeStatus(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
2428
3011
  }
2429
3012
  if (command === "bootstrap") {
2430
- return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot });
3013
+ return bootstrapWorktree(context, { protocolId: requiredOption(parsed, "protocol-id"), projectRoot: scopeProjectRoot }, prepared.worktreeBootstrap);
2431
3014
  }
2432
3015
  if (command === "close") {
2433
3016
  return closeWorktree(context, {
@@ -2439,52 +3022,50 @@ function dispatchWorktree(context, command, parsed, scopeProjectRoot) {
2439
3022
  throw new AppError("usage", `Unknown worktree command: ${command ?? "<empty>"}`, 2);
2440
3023
  }
2441
3024
  function dispatchPlanItem(context, parsed, projectRoot) {
3025
+ return updatePlanItem(context, planItemInput(parsed, projectRoot));
3026
+ }
3027
+ function planItemInput(parsed, projectRoot) {
2442
3028
  const action = requiredPosition(parsed, 0, "plan item action");
2443
3029
  const protocolId = requiredPosition(parsed, 1, "protocol-id");
2444
3030
  const itemId = requiredPosition(parsed, 2, "item-id");
2445
3031
  if (action === "start") {
2446
- return startPlanItem(context, { protocolId, projectRoot, itemId });
3032
+ return { protocolId, projectRoot, itemId, action };
2447
3033
  }
2448
3034
  if (action === "done") {
2449
- return completePlanItem(context, {
3035
+ return {
3036
+ action,
2450
3037
  protocolId,
2451
3038
  projectRoot,
2452
3039
  itemId,
2453
3040
  summary: requiredOption(parsed, "summary"),
2454
3041
  evidence: parsed.options.get("evidence") ?? []
2455
- });
3042
+ };
2456
3043
  }
2457
3044
  if (action === "block") {
2458
- return blockPlanItem(context, {
3045
+ return {
3046
+ action,
2459
3047
  protocolId,
2460
3048
  projectRoot,
2461
3049
  itemId,
2462
3050
  reason: requiredOption(parsed, "reason"),
2463
3051
  userRequired: parseBoolean(requiredOption(parsed, "user-required"), "user-required")
2464
- });
3052
+ };
2465
3053
  }
2466
3054
  if (action === "skip") {
2467
- return skipPlanItem(context, {
3055
+ return {
3056
+ action,
2468
3057
  protocolId,
2469
3058
  projectRoot,
2470
3059
  itemId,
2471
3060
  reason: requiredOption(parsed, "reason")
2472
- });
3061
+ };
2473
3062
  }
2474
3063
  throw new AppError("usage", `Unknown plan item action: ${action}`, 2);
2475
3064
  }
2476
3065
  function readOptionalJsonFile(file) {
2477
3066
  if (!file)
2478
3067
  return undefined;
2479
- if (!fs.existsSync(file)) {
2480
- throw new AppError("not_found", `JSON file does not exist: ${file}`, 1);
2481
- }
2482
- try {
2483
- return JSON.parse(fs.readFileSync(file, "utf8"));
2484
- }
2485
- catch {
2486
- throw new AppError("validation", `JSON file is malformed: ${file}`, 2);
2487
- }
3068
+ return prepareJsonFile(file, "JSON input").value;
2488
3069
  }
2489
3070
  function parseFlowFlagOverrides(values) {
2490
3071
  const result = {};
@@ -2509,21 +3090,6 @@ function parseFlowFlagOverrides(values) {
2509
3090
  function parseArgs(args) {
2510
3091
  return parseCommandArgs(args);
2511
3092
  }
2512
- /** Most CLI options name one authority, path or target. Silently taking the
2513
- * last duplicate can widen a mutation after an operator typo. Keep only the
2514
- * options consumed as intentional arrays repeatable. */
2515
- function assertSingletonOptions(args) {
2516
- const repeatable = new Set(["alias", "flag", "hide", "check-ref", "evidence", "from-check", "from-finding", "from-unresolved", "on", "origin-work"]);
2517
- const parsed = parseCommandArgs(args);
2518
- if (args[0] === "stage" && args[1] === "finish"
2519
- && ["result-file", "semantic-file", "data", "result-stdin"].filter(name => parsed.options.has(name)).length > 1) {
2520
- throw new AppError("usage", "Stage finish requires exactly one result input; result-file, semantic-file, data and result-stdin are mutually exclusive", 2);
2521
- }
2522
- for (const [name, values] of parsed.options) {
2523
- if (!repeatable.has(name) && values.length > 1)
2524
- throw new AppError("usage", `--${name} may be supplied only once`, 2);
2525
- }
2526
- }
2527
3093
  function requiredPosition(parsed, index, label) {
2528
3094
  const value = parsed.positional[index];
2529
3095
  if (!value) {
@@ -2534,7 +3100,11 @@ function requiredPosition(parsed, index, label) {
2534
3100
  function optionalOption(parsed, key) {
2535
3101
  const values = parsed.options.get(key);
2536
3102
  const value = values?.[values.length - 1];
2537
- return value === flagValue ? undefined : value;
3103
+ if (value === flagValue)
3104
+ throw new AppError("usage", `--${key} requires a value`, 2);
3105
+ if (value !== undefined && !value.trim())
3106
+ throw new AppError("usage", `--${key} requires a non-empty value`, 2);
3107
+ return value;
2538
3108
  }
2539
3109
  function allOptions(parsed, key) {
2540
3110
  return (parsed.options.get(key) ?? []).filter((value) => value !== flagValue);
@@ -2769,14 +3339,36 @@ function projectRootForMergeQueueMutation(context, command, parsed) {
2769
3339
  function protocolProjectRoot(context, protocolId) {
2770
3340
  return requireProtocol(context, protocolId).project_root;
2771
3341
  }
3342
+ function prepareCliWorkRepair(context, parsed, objective, scopeProjectRoot) {
3343
+ 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") ?? [];
3344
+ if ([findings.length > 0, unresolved.length > 0, receipts.length > 0].filter(Boolean).length !== 1)
3345
+ throw new AppError("usage", "Repair requires exactly one source: --from-check, --from-finding or --from-unresolved", 2);
3346
+ const scope = { projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"), runId: requiredOption(parsed, "run"), objective };
3347
+ if (findings.length) {
3348
+ if (origins.length || hasOption(parsed, "verification-file"))
3349
+ throw new AppError("usage", "Finding repair derives its origins and cannot accept --origin-work or --verification-file", 2);
3350
+ return prepareVnextCodeReviewRepair(context, { ...scope, findingIds: findings, checkRefsByFinding: Object.fromEntries(findings.map(finding => [finding, parsed.options.get("check-ref") ?? []])) });
3351
+ }
3352
+ if (!origins.length)
3353
+ throw new AppError("usage", "Repair requires at least one --origin-work", 2);
3354
+ if (hasOption(parsed, "check-ref"))
3355
+ throw new AppError("usage", "--check-ref is only supported with --from-finding", 2);
3356
+ if (receipts.length > 1)
3357
+ throw new AppError("usage", "Create one repair Work per --from-check receipt", 2);
3358
+ if (!unresolved.length && hasOption(parsed, "verification-file"))
3359
+ throw new AppError("usage", "--verification-file is only supported with --from-unresolved", 2);
3360
+ const input = { ...scope, originWorkIds: origins, ...(unresolved.length ? { semanticUnresolved: unresolved, verificationPath: requiredOption(parsed, "verification-file") } : { checkReceiptId: receipts[0] }) };
3361
+ return { input, value: prepareVnextCodeRepair(context, input) };
3362
+ }
3363
+ function workerPromptInput(parsed) {
3364
+ const workspaceRoot = optionalOption(parsed, "workspace-root"), taskFile = optionalOption(parsed, "task-file");
3365
+ 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 } : {}) };
3366
+ }
2772
3367
  function runnerIntake(context) {
2773
3368
  const input = context.value.task_input?.[0];
2774
3369
  if (!input?.path)
2775
3370
  throw new AppError("validation", "Runner SPECIFY context requires one ordered task_input file", 2);
2776
- const file = path.resolve(input.path);
2777
- if (!fs.existsSync(file) || !fs.statSync(file).isFile())
2778
- throw new AppError("not_found", "Runner SPECIFY task input is missing", 2, { task_input: file });
2779
- return fs.readFileSync(file, "utf8");
3371
+ return prepareTextFile(input.path, "Runner SPECIFY task input").text;
2780
3372
  }
2781
3373
  /** Stage services install context before their lifecycle transition. Keeping
2782
3374
  * this check here makes an incomplete service migration fail closed instead of