@deksden-com/dd-flow-cli 0.9.0-beta.124 → 0.9.0-beta.127

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 (113) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +10 -1
  3. package/dist/build-info.json +7 -7
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +4 -3
  6. package/dist/cli/help.js +1 -0
  7. package/dist/cli/hook-ingress.js +1 -1
  8. package/dist/cli/run-cli.js +126 -193
  9. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  10. package/dist/cli/runtime-maintenance-ingress.js +51 -0
  11. package/dist/cli/runtime-process.js +128 -0
  12. package/dist/cli.js +8 -5
  13. package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
  14. package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
  15. package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
  16. package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
  17. package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
  18. package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
  19. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  20. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  21. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  22. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  23. package/dist/harness-runtime/lib/daemon-operations.mjs +60 -13
  24. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +151 -72
  25. package/dist/harness-runtime/lib/dd-agy.mjs +11 -23
  26. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +9 -14
  27. package/dist/harness-runtime/lib/dd-codex.mjs +252 -80
  28. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +14 -19
  29. package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
  30. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +56 -30
  31. package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
  32. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +18 -27
  33. package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
  34. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +56 -53
  35. package/dist/harness-runtime/lib/dd-zcode.mjs +200 -111
  36. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  37. package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
  38. package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
  39. package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
  40. package/dist/harness-runtime/lib/managed-daemon.d.mts +10 -1
  41. package/dist/harness-runtime/lib/managed-daemon.mjs +228 -40
  42. package/dist/harness-runtime/lib/native-children.d.mts +13 -0
  43. package/dist/harness-runtime/lib/native-children.mjs +78 -0
  44. package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
  45. package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
  46. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  47. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  48. package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
  49. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  50. package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
  51. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  52. package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
  53. package/dist/harness-runtime/lib/process-json.mjs +136 -38
  54. package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
  55. package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
  56. package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
  57. package/dist/runtime/context.js +8 -4
  58. package/dist/schemas/run-control-receipt.schema.json +19 -0
  59. package/dist/services/code-checks.js +79 -9
  60. package/dist/services/codex-capacity.js +117 -41
  61. package/dist/services/codex-hook-delivery.js +2 -2
  62. package/dist/services/compatibility-preflight.js +4 -1
  63. package/dist/services/controller-fanout.js +9 -76
  64. package/dist/services/engines.js +70 -20
  65. package/dist/services/eval-snapshots.js +179 -40
  66. package/dist/services/external-work-launch.js +13 -8
  67. package/dist/services/harness-adapter.js +175 -11
  68. package/dist/services/hooks.js +65 -21
  69. package/dist/services/lifecycle-invocations.js +404 -32
  70. package/dist/services/managed-lease-monitor.js +128 -0
  71. package/dist/services/managed-processes.js +103 -47
  72. package/dist/services/merge-server.js +58 -21
  73. package/dist/services/recovery-observation-budget.js +67 -7
  74. package/dist/services/recovery-snapshot-database.js +7 -4
  75. package/dist/services/run-control-receipt.js +1 -1
  76. package/dist/services/run-control-worker.js +177 -118
  77. package/dist/services/run-control.js +99 -8
  78. package/dist/services/run-controller-adapter.js +169 -46
  79. package/dist/services/run-controller-capture.js +14 -2
  80. package/dist/services/run-controller-process.js +11 -4
  81. package/dist/services/run-controller-state.js +68 -7
  82. package/dist/services/run-controller.js +166 -94
  83. package/dist/services/run-fork.js +8 -3
  84. package/dist/services/run-recovery-guard.js +75 -0
  85. package/dist/services/run-recovery-runtime.js +1 -56
  86. package/dist/services/run-recovery.js +17 -13
  87. package/dist/services/runs.js +39 -6
  88. package/dist/services/runtime-budget.js +32 -15
  89. package/dist/services/runtime-scope-control.js +8 -0
  90. package/dist/services/runtime-scope-resume.js +30 -8
  91. package/dist/services/runtime-scope-worker.js +118 -73
  92. package/dist/services/runtime-service.js +6 -7
  93. package/dist/services/scope-writer-cleanup.js +3 -2
  94. package/dist/services/stage-context.js +135 -0
  95. package/dist/services/stage-lifecycle.js +3 -15
  96. package/dist/services/stage-pause.js +3 -2
  97. package/dist/services/usage.js +14 -3
  98. package/dist/services/vnext-code-review.js +10 -9
  99. package/dist/services/vnext-code.js +10 -8
  100. package/dist/services/vnext-contracts.js +10 -1
  101. package/dist/services/vnext-execution-profile.js +75 -2
  102. package/dist/services/vnext-fanout.js +5 -0
  103. package/dist/services/vnext-merge.js +10 -9
  104. package/dist/services/vnext-plan-review.js +21 -17
  105. package/dist/services/vnext-plan.js +9 -7
  106. package/dist/services/vnext-protocolize.js +15 -9
  107. package/dist/services/vnext-specify.js +6 -6
  108. package/dist/services/work-registry.js +34 -4
  109. package/dist/services/workspace-bootstrap.js +14 -3
  110. package/dist/shared/errors.js +11 -6
  111. package/dist/storage/agent-output.js +26 -0
  112. package/dist/storage/database.js +80 -21
  113. package/package.json +7 -4
@@ -14,6 +14,7 @@ import { appendFlowRunTimelineEvent, pauseFlowRunStage, resumeFlowRunStage } fro
14
14
  import { bindSessionForResume, refreshRunWorkProjection } from "./work-registry.js";
15
15
  import { assertRunMutationAllowed } from "./run-recovery.js";
16
16
  import { runtimeCommand } from "./runtime-command.js";
17
+ import { prepareAgentOutputFile } from "../storage/agent-output.js";
17
18
  export { flowCommand } from "./runtime-command.js";
18
19
  export function prepareStagePause(context, input) {
19
20
  if (typeof input.question !== "string" || !input.question.trim())
@@ -38,6 +39,7 @@ export function pauseStageForUser(context, input) {
38
39
  const questionPath = path.join(pauseRoot, "question.md");
39
40
  fs.mkdirSync(pauseRoot, { recursive: true });
40
41
  publishImmutableInput(questionPath, Buffer.from(question), "hitl_question_conflict");
42
+ const answerInputPath = prepareAgentOutputFile(path.join(pauseRoot, "answer-input.md"));
41
43
  const now = context.now();
42
44
  context.db.writeTransaction(() => {
43
45
  const paused = context.db.run("UPDATE works SET status = 'paused', updated_at = ? WHERE work_id = ? AND status = 'running'", [now, work.work_id]);
@@ -46,7 +48,6 @@ export function pauseStageForUser(context, input) {
46
48
  pauseFlowRunStage(context, { projectRoot, runId: run.id, stage: stage.stage, workId: work.work_id, pauseId, questionPath });
47
49
  context.db.afterCommit(() => refreshRunWorkProjection(context, project.id, run.id));
48
50
  });
49
- const answerInputPath = path.join(pauseRoot, "answer-input.md");
50
51
  const resume = stageResumeCommand(context, { runId: run.id, stage: stage.stage, workId: work.work_id, projectRoot, answerInputPath });
51
52
  const resumeTemplate = `Write the complete user answer exactly to ${quote([answerInputPath])}, then run:\n${resume}`;
52
53
  return {
@@ -146,7 +147,7 @@ export function resumeStageAfterUser(context, input) {
146
147
  export function stagePauseCommand(context, input) {
147
148
  const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
148
149
  const run = requireRun(context, project.id, input.runId);
149
- const questionInputPath = path.join(requireRunHome(run), "works", input.workId, "question-input.md");
150
+ const questionInputPath = prepareAgentOutputFile(path.join(requireRunHome(run), "works", input.workId, "question-input.md"));
150
151
  return managedLifecycleCommand(context, runtimeCommand(context, ["stage", "pause", shortEntityReference(input.runId), "--stage", input.stage, "--work", shortEntityReference(input.workId), "--question-file", questionInputPath, "--project-root", input.projectRoot, "--json"]));
151
152
  }
152
153
  /** Render separate file-write and lifecycle-call instructions for HITL. */
@@ -369,7 +369,7 @@ function usageRowsForPhysicalSessions(context, links, observedAt, stage) {
369
369
  : completedTurn?.completed_at ?? sessionLinks.map((item) => item.completed_at ?? observedAt).sort().at(-1);
370
370
  const endedAt = stage?.completedAt ?? (Date.parse(naturalEnd) > Date.parse(observedAt) ? observedAt : naturalEnd);
371
371
  const harnessUsage = isExternalHarness(link.harness)
372
- ? readHarnessUsageWindow(context, link.harness, link.project_id, link.provider_session_id, startedAt, endedAt, observedAt)
372
+ ? readHarnessUsageWindow(context, link.harness, link.project_id, link.provider_session_id, startedAt, endedAt, observedAt, Boolean(stage))
373
373
  : null;
374
374
  const parsed = harnessUsage ?? readCodexTranscriptWindow(link.transcript_path, nativeSessionId(link), startedAt, endedAt);
375
375
  return {
@@ -393,18 +393,29 @@ function hasInclusiveUsageSnapshot(context, projectId, harness, providerSessionI
393
393
  return false;
394
394
  return Boolean(context.db.get("SELECT 1 FROM harness_usage_snapshots WHERE project_id = ? AND harness = ? AND provider_session_id = ? AND usage_scope = 'execution_tree_inclusive' AND observed_at <= ? LIMIT 1", [projectId, harness, providerSessionId, observedAt]));
395
395
  }
396
- function readHarnessUsageWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt) {
396
+ function readHarnessUsageWindow(context, harness, projectId, providerSessionId, startedAt, endedAt, observedAt, stageWindow) {
397
397
  if (!providerSessionId)
398
398
  return { status: "not_observable", diagnostic: "provider_session_id_missing" };
399
399
  const snapshots = context.db.all(`SELECT completeness, observed_at, total_tokens, input_tokens, cache_read_input_tokens, cache_write_input_tokens, output_tokens, reasoning_output_tokens
400
400
  FROM harness_usage_snapshots
401
401
  WHERE project_id = ? AND harness = ? AND provider_session_id = ? AND observed_at <= ?
402
402
  ORDER BY observed_at, id`, [projectId, harness, providerSessionId, observedAt]);
403
- const last = snapshots.find((snapshot) => Date.parse(snapshot.observed_at) > Date.parse(endedAt))
403
+ const last = (!stageWindow ? snapshots.find((snapshot) => Date.parse(snapshot.observed_at) > Date.parse(endedAt)) : undefined)
404
404
  ?? snapshots.filter((snapshot) => Date.parse(snapshot.observed_at) <= Date.parse(endedAt)).at(-1);
405
405
  const before = snapshots.filter((snapshot) => Date.parse(snapshot.observed_at) <= Date.parse(startedAt)).at(-1);
406
406
  if (!last || !before || last === before)
407
407
  return { status: "not_yet_emitted", diagnostic: `${harness}_usage_window_incomplete` };
408
+ // Cumulative counters outside an attempt's boundaries cannot establish its
409
+ // spend: adjacent attempts would otherwise charge overlapping intervals.
410
+ if (stageWindow && (Date.parse(before.observed_at) !== Date.parse(startedAt)
411
+ || Date.parse(last.observed_at) !== Date.parse(endedAt)
412
+ || before.completeness !== "complete" || last.completeness !== "complete")) {
413
+ return { status: "not_observable", diagnostic: `${harness}_stage_window_unconfirmed` };
414
+ }
415
+ if (stageWindow && ["total_tokens", "input_tokens", "cache_read_input_tokens", "cache_write_input_tokens", "output_tokens", "reasoning_output_tokens"]
416
+ .some(key => last[key] !== null && before[key] !== null && last[key] < before[key])) {
417
+ return { status: "not_observable", diagnostic: `${harness}_stage_counter_reset` };
418
+ }
408
419
  if (harness === "droid-cli" && (last.completeness !== "complete" || before.completeness !== "complete"))
409
420
  return { status: "not_observable", diagnostic: "droid-cli_usage_window_partial" };
410
421
  const delta = (value, baseline) => value === null || baseline === null ? null : Math.max(0, value - baseline);
@@ -18,7 +18,7 @@ import { withStageSettlement, prepareFlowRunStageAttachment } from "./runs.js";
18
18
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
19
19
  import { acceptStageReport, prepareStageReport } from "./stage-report-renderer.js";
20
20
  import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix, publishVerificationMatrix } from "./verification-matrix.js";
21
- import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
21
+ import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
22
22
  import { readFanoutDescriptor, writeFanoutDescriptor } from "./vnext-fanout.js";
23
23
  import { stageGraphScope } from "./stage-work-graph.js";
24
24
  import { prepareVnextMergeRequest } from "./vnext-merge.js";
@@ -27,6 +27,7 @@ import { assertRunMutationAllowed } from "./run-recovery.js";
27
27
  import { prepareJsonFile, prepareOutputFile } from "../cli/input-preparation.js";
28
28
  import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
29
29
  import { reviewDefEvidence } from "./review-deferral.js";
30
+ import { stageSuccessor } from "./vnext-contracts.js";
30
31
  const stage = "code-review";
31
32
  const stageDir = vnextStageDirectory(stage);
32
33
  const baselineAspects = ["goal_traceability", "coding_standards_design_review", "verification_evidence_review"];
@@ -76,11 +77,11 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
76
77
  if (prepared.resumed) {
77
78
  if (effectiveMode(prepared.home, run).mode !== "off")
78
79
  resumeVerificationMatrix(context, project.id, run.id, stage);
79
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath: existingPrompt, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
80
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath: existingPrompt, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
80
81
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath: existingPrompt, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
81
- const prompt = fs.readFileSync(existingPrompt, "utf8");
82
+ const delivered = currentStagePrompt(context, existingPrompt);
82
83
  const orchestration = prepared.orchestration;
83
- return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.prior, id: binding.work_session_id, prompt_path: existingPrompt, worker_prompt_markdown: prompt, ...(externalContext ? { external_context: externalContext } : {}), ...(orchestration ? { orchestration } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
84
+ return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.prior, id: binding.work_session_id, ...delivered, ...(externalContext ? { external_context: externalContext } : {}), ...(orchestration ? { orchestration } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
84
85
  }
85
86
  const { mode, cycle, groups, batch, items, template, checks, codeReportSha256 } = prepared;
86
87
  prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" }, true);
@@ -96,17 +97,17 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
96
97
  fs.writeFileSync(path.join(root, "decision.json"), `${JSON.stringify(disabledReviewDecision(), null, 2)}\n`);
97
98
  const prompt = `<stage_identity>\n- RUN: ${run.id}\n- stage: code-review\n- mode: off\n</stage_identity>\n\nCODE-REVIEW is disabled by the frozen RUN configuration. Finish with: ${finishCommand(context, run.id, projectRoot, path.join(root, "decision.json"))}\n${stageLifecycleInstruction}\n`;
98
99
  fs.writeFileSync(promptPath, prompt);
99
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
100
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
100
101
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
101
102
  const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" };
102
103
  attachFlowRunStage(context, attachmentInput, prepareFlowRunStageAttachment(context, attachmentInput));
103
- return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
104
+ return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}), next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
104
105
  }
105
106
  const batchFile = path.join(root, "review-work-batch.json");
106
107
  fs.writeFileSync(batchFile, `${JSON.stringify(batch, null, 2)}\n`);
107
108
  const prompt = `${orchestratorPrompt(context, { projectRoot, run, root, rootWork, mode: mode.mode, groups, template, checks })}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, prepared.home, "reviewing whether current evidence proves the accepted obligations")}\n`;
108
109
  fs.writeFileSync(promptPath, prompt);
109
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
110
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
110
111
  const binding = bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
111
112
  const registered = addWorkBatch(context, { parentWorkId: rootWork.work_id, value: batch, prepared: items, stage, cycle });
112
113
  const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
@@ -114,7 +115,7 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
114
115
  const attachment = prepareFlowRunStageAttachment(context, attachmentInput);
115
116
  commitVerificationMatrixStart(context, prepared.matrix, () => attachFlowRunStage(context, attachmentInput, attachment));
116
117
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "code_review_started", work_id: rootWork.work_id, mode: mode.mode, mode_source: mode.source, groups, registered });
117
- return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, review: { groups, works: registered }, next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
118
+ return { ok: true, run_id: run.id, stage, mode: mode.mode, mode_source: mode.source, id: binding.work_session_id, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}), orchestration, review: { groups, works: registered }, next: { finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
118
119
  }
119
120
  export function prepareVnextCodeReviewRepair(context, input) {
120
121
  if (!Array.isArray(input.findingIds) || !input.findingIds.length || input.findingIds.some(id => typeof id !== "string" || !id.trim()))
@@ -293,7 +294,7 @@ export async function finishVnextCodeReview(context, input) {
293
294
  }
294
295
  refreshRunWorkProjection(context, project.id, run.id);
295
296
  });
296
- return { ok: true, run_id: run.id, stage, outcome, report_path: path.join(root, "stage-report.json"), next_action: nextAction, ...(mergeRequest ? { merge_request: mergeRequest, next: { kind: "start_stage", stage: "merge", command: managedLifecycleCommand(context, runtimeCommand(context, ["stage", "start", run.id, "--stage", "merge", "--project-root", projectRoot, "--json", "--progress-jsonl"])) } } : {}) };
297
+ return { ok: true, run_id: run.id, stage, outcome, report_path: path.join(root, "stage-report.json"), next_action: nextAction, ...(mergeRequest ? { merge_request: mergeRequest, next: stageSuccessor(context, { projectRoot, runId: run.id, stage: "merge", command: runtimeCommand(context, ["stage", "start", run.id, "--stage", "merge", "--project-root", projectRoot, "--json", "--progress-jsonl"]) }) } : {}) };
297
298
  }
298
299
  export function readCodeReviewDecision(file, accepted, explicit = false, prepared) {
299
300
  if (!accepted)
@@ -4,6 +4,7 @@ import { ensureAggregateRepair, ensureRepairIntent } from "./repair-intents.js";
4
4
  import { runWorkspaceBootstrap } from "./workspace-bootstrap.js";
5
5
  import crypto from "node:crypto";
6
6
  import fs from "node:fs";
7
+ import { prepareAgentOutputFile } from "../storage/agent-output.js";
7
8
  import path from "node:path";
8
9
  import { AppError } from "../shared/errors.js";
9
10
  import { resolveProjectRoot } from "../storage/paths.js";
@@ -21,11 +22,12 @@ import { vnextStageDirectory } from "../domain/stage-catalog.js";
21
22
  import { acceptStageReport, prepareStageReport } from "./stage-report-renderer.js";
22
23
  import { prepareVnextMergeRequest } from "./vnext-merge.js";
23
24
  import { assertStageStartHookEvent } from "./hooks.js";
24
- import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
25
+ import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
25
26
  import { readFanoutDescriptor, writeFanoutDescriptor } from "./vnext-fanout.js";
26
27
  import { assertRunMutationAllowed } from "./run-recovery.js";
27
28
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
28
29
  import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, publishVerificationMatrix, latestRunVerificationMatrix } from "./verification-matrix.js";
30
+ import { stageSuccessor } from "./vnext-contracts.js";
29
31
  const stage = "code";
30
32
  const stageDir = vnextStageDirectory("code");
31
33
  /** A controller prepares slow, deterministic project tooling before it asks an
@@ -93,12 +95,13 @@ export async function startVnextCode(context, input, prepared = prepareVnextCode
93
95
  const { projectRoot, project, run, root, promptPath, rootWork, sourceRepairAttempt } = prepared;
94
96
  assertRunMutationAllowed(context, project.id, run.id);
95
97
  assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
98
+ prepareAgentOutputFile(path.join(requireHome(run), "works", rootWork.work_id, "block-summary.md"));
96
99
  if (prepared.resumed) {
97
100
  resumeVerificationMatrix(context, project.id, run.id, stage);
98
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
101
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
99
102
  bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
100
103
  const orchestration = prepared.orchestration ?? writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
101
- return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.existingStage, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, graph: codeWorkGraph(context, project.id, run.id), next: { refresh_graph_command: runtimeCommand(context, ["work", "ls", "--run", run.id, "--ready", "--project-root", projectRoot, "--json"]), finish_command: finishCommand(context, input.runId, projectRoot, path.join(root, "code-verification.json")) } };
104
+ return { ok: true, resumed: true, run_id: run.id, stage, stage_status: prepared.existingStage, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}), orchestration, graph: codeWorkGraph(context, project.id, run.id), next: { refresh_graph_command: runtimeCommand(context, ["work", "ls", "--run", run.id, "--ready", "--project-root", projectRoot, "--json"]), finish_command: finishCommand(context, input.runId, projectRoot, path.join(root, "code-verification.json")) } };
102
105
  }
103
106
  if (!sourceRepairAttempt)
104
107
  prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" }, true);
@@ -108,7 +111,7 @@ export async function startVnextCode(context, input, prepared = prepareVnextCode
108
111
  const graph = codeWorkGraph(context, project.id, run.id);
109
112
  const prompt = `${coordinatorPrompt(context, { projectRoot, run, root, rootWork, readiness, template: prepared.template, capacity: prepared.capacity, graph })}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, prepared.home, "implementation and code-verification.json")}\n`;
110
113
  fs.writeFileSync(promptPath, prompt);
111
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
114
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
112
115
  const binding = bindStageCoordinatorWork(context, {
113
116
  workId: rootWork.work_id,
114
117
  hookEventId: input.hookEventId,
@@ -139,8 +142,7 @@ export async function startVnextCode(context, input, prepared = prepareVnextCode
139
142
  stage,
140
143
  root_work_id: rootWork.work_id,
141
144
  id: binding.work_session_id,
142
- prompt_path: promptPath,
143
- worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"),
145
+ ...currentStagePrompt(context, promptPath),
144
146
  ...(externalContext ? { external_context: externalContext } : {}),
145
147
  graph,
146
148
  orchestration,
@@ -332,7 +334,7 @@ export async function finishVnextCode(context, input) {
332
334
  outcome: "accepted",
333
335
  report_path: path.join(root, "stage-report.json"),
334
336
  next_action: next,
335
- ...(next === "start_code_review" ? { next: { kind: "start_stage", stage: "code-review", command: managedLifecycleCommand(context, runtimeCommand(context, ["stage", "start", run.id, "--stage", "code-review", "--project-root", projectRoot, "--json"])) } } : next === "start_merge" ? { merge_request: mergeRequest, next: { kind: "start_stage", stage: "merge", command: managedLifecycleCommand(context, runtimeCommand(context, ["stage", "start", run.id, "--stage", "merge", "--project-root", projectRoot, "--json", "--progress-jsonl"])) } } : {})
337
+ ...(next === "start_code_review" ? { next: stageSuccessor(context, { projectRoot, runId: run.id, stage: "code-review", command: runtimeCommand(context, ["stage", "start", run.id, "--stage", "code-review", "--project-root", projectRoot, "--json"]) }) } : next === "start_merge" ? { merge_request: mergeRequest, next: stageSuccessor(context, { projectRoot, runId: run.id, stage: "merge", command: runtimeCommand(context, ["stage", "start", run.id, "--stage", "merge", "--project-root", projectRoot, "--json", "--progress-jsonl"]) }) } : {})
336
338
  };
337
339
  }
338
340
  export function registerAggregateCodeRepair(context, input) {
@@ -539,7 +541,7 @@ function receiptRepairPaths(workspaceRoot, receipt) {
539
541
  }
540
542
  return [...paths].sort();
541
543
  }
542
- function coordinatorPrompt(context, input) {
544
+ export function coordinatorPrompt(context, input) {
543
545
  const { graph, capacity, template } = input;
544
546
  return [
545
547
  "<stage_identity>",
@@ -1 +1,10 @@
1
- export {};
1
+ import { isControllerManagedInvocation, managedLifecycleCommand } from "./lifecycle-invocations.js";
2
+ /** Successor authority belongs to the controller, not the finishing Session.
3
+ * Manual flows still need an executable next step. Current-stage finish and
4
+ * paused/child Work commands do not pass through this boundary. */
5
+ export function stageSuccessor(context, input) {
6
+ const next = { ...input.metadata, kind: "start_stage", stage: input.stage };
7
+ return isControllerManagedInvocation(context, input.projectRoot, input.runId)
8
+ ? { ...next, controller_owned: true, stop_turn: true, instruction: "This Stage is complete. End this Turn now and await the controller's next Stage assignment. Do not start the successor Stage or prepare its command." }
9
+ : { ...next, command: managedLifecycleCommand(context, input.command) };
10
+ }
@@ -1,18 +1,89 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import crypto from "node:crypto";
4
+ import { isDeepStrictEqual } from "node:util";
3
5
  import { AppError } from "../shared/errors.js";
6
+ import { canonicalPath } from "../storage/paths.js";
4
7
  import { isExecutionRouting } from "./execution-policy.js";
8
+ export const RUN_OPERATIONAL_DECISION_CONTRACT = "run-operational-decision@1";
9
+ const object = (value) => Boolean(value && typeof value === "object" && !Array.isArray(value));
10
+ const text = (value) => typeof value === "string" && Boolean(value.trim());
11
+ const keys = (value, allowed) => object(value) && Object.keys(value).every(key => allowed.includes(key));
12
+ /** Structural validation only: acceptance is supplied by the caller, never inferred from settings. */
13
+ export function validateRunOperationalDecision(value, settings) {
14
+ if (!object(value) || value.schema_id !== RUN_OPERATIONAL_DECISION_CONTRACT || !text(value.id)
15
+ || !object(value.scope) || !text(value.scope.project_root) || !path.isAbsolute(value.scope.project_root)
16
+ || [value.scope.eval_id, value.scope.execution_id].some(item => item !== undefined && !text(item))
17
+ || !object(value.settings) || !object(value.settings.merge_delivery) || value.settings.merge_delivery.strategy !== settings.merge_delivery.strategy
18
+ || !object(value.settings.merge_cleanup) || value.settings.merge_cleanup.source !== settings.merge_cleanup.source
19
+ || !object(value.policy) || !text(value.policy.path) || !text(value.policy.sha256) || !/^[a-f0-9]{64}$/.test(value.policy.sha256)
20
+ || !Array.isArray(value.policy.clauses) || !value.policy.clauses.length || !value.policy.clauses.every(text)
21
+ || !object(value.acceptance) || !text(value.acceptance.source) || !text(value.acceptance.reference)
22
+ || value.exception !== undefined && (!object(value.exception) || !text(value.exception.reason) || !text(value.exception.owner)
23
+ || !text(value.exception.review_at) || !Number.isFinite(Date.parse(value.exception.review_at))
24
+ || value.exception.expires_at !== undefined && (!text(value.exception.expires_at) || !Number.isFinite(Date.parse(value.exception.expires_at))))) {
25
+ throw new AppError("operational_decision_invalid", "Operational decision requires explicit acceptance, scoped authority and matching settings", 2);
26
+ }
27
+ if (!keys(value, ["schema_id", "id", "scope", "settings", "policy", "acceptance", "exception", "declaration"])
28
+ || !keys(value.scope, ["project_root", "eval_id", "execution_id"])
29
+ || !keys(value.settings, ["merge_delivery", "merge_cleanup"])
30
+ || !keys(value.settings.merge_delivery, ["strategy"]) || !keys(value.settings.merge_cleanup, ["source"])
31
+ || !keys(value.policy, ["path", "sha256", "clauses"]) || !keys(value.acceptance, ["source", "reference"])
32
+ || value.exception !== undefined && !keys(value.exception, ["reason", "owner", "review_at", "expires_at"])
33
+ || value.declaration !== undefined && !keys(value.declaration, ["bytes", "sha256"]))
34
+ throw new AppError("operational_decision_invalid", "Operational decision contains unsupported authority fields", 2);
35
+ if (value.declaration !== undefined) {
36
+ if (!object(value.declaration) || !text(value.declaration.bytes) || !text(value.declaration.sha256)
37
+ || crypto.createHash("sha256").update(value.declaration.bytes).digest("hex") !== value.declaration.sha256)
38
+ throw new AppError("operational_decision_invalid", "Original operational declaration checksum does not match", 2);
39
+ let declaration;
40
+ try {
41
+ declaration = JSON.parse(value.declaration.bytes);
42
+ }
43
+ catch {
44
+ throw new AppError("operational_decision_invalid", "Original operational declaration must be JSON", 2);
45
+ }
46
+ if (!object(declaration) || !keys(declaration, ["schema_id", "id", "scope", "settings", "policy", "acceptance", "exception"])
47
+ || declaration.scope !== undefined && !keys(declaration.scope, ["project_root", "eval_id", "execution_id"])
48
+ || ["schema_id", "id", "settings", "policy", "acceptance", "exception"].some(key => !isDeepStrictEqual(declaration[key], value[key])))
49
+ throw new AppError("operational_decision_invalid", "Materialized decision changed the accepted declaration", 2);
50
+ }
51
+ return value;
52
+ }
53
+ /** Machine validation of declared policy bytes, not an NLP interpretation of Markdown. */
54
+ export function assertOperationalDecisionSource(decision, projectRoot, now) {
55
+ if (canonicalPath(decision.scope.project_root, false) !== canonicalPath(projectRoot, false))
56
+ throw new AppError("operational_decision_scope_mismatch", "Operational decision belongs to another project", 2);
57
+ const file = path.resolve(projectRoot, decision.policy.path);
58
+ const relative = path.relative(fs.realpathSync(projectRoot), fs.existsSync(file) ? fs.realpathSync(file) : file);
59
+ if (relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative) || relative === ".." || !fs.existsSync(file) || crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex") !== decision.policy.sha256)
60
+ throw new AppError("operational_decision_policy_changed", "Operational decision policy source checksum does not match an owned file", 2, { file });
61
+ if (decision.exception?.expires_at && Date.parse(decision.exception.expires_at) <= Date.parse(now))
62
+ throw new AppError("operational_decision_expired", "Explicit operational decision expiration has passed", 2);
63
+ }
5
64
  const relativePath = path.join(".memory-bank", "dd-flow", "project-execution.json");
6
65
  /** The project owns defaults; a RUN snapshots them once and never re-infers them. */
7
66
  export function loadVnextExecutionProfile(projectRoot) {
67
+ return loadVnextExecutionProfileSnapshot(projectRoot).profile;
68
+ }
69
+ /** Parse and identify the same source bytes; a second read cannot bind another declaration. */
70
+ export function loadVnextExecutionProfileSnapshot(projectRoot) {
8
71
  const file = path.join(projectRoot, relativePath);
72
+ let bytes;
9
73
  let value;
10
74
  try {
11
- value = JSON.parse(fs.readFileSync(file, "utf8"));
75
+ bytes = fs.readFileSync(file);
76
+ value = JSON.parse(bytes.toString("utf8"));
12
77
  }
13
78
  catch (error) {
14
79
  throw new AppError("execution_profile_missing", "vNext flow requires .memory-bank/dd-flow/project-execution.json", 1, { file, cause: String(error) });
15
80
  }
81
+ return { profile: validateVnextExecutionProfile(value), bytes };
82
+ }
83
+ /** Pure shared admission contract for project-execution@2 and @3. */
84
+ export function validateVnextExecutionProfile(value) {
85
+ if (!object(value))
86
+ throw new AppError("execution_profile_invalid", "Project execution profile must be an object", 1);
16
87
  const profile = value;
17
88
  if ((profile.schema_id !== "dd-flow/project-execution@2" && profile.schema_id !== "dd-flow/project-execution@3")
18
89
  || (profile.stage_session_mode !== undefined && profile.stage_session_mode !== "same_session" && profile.stage_session_mode !== "new_session")
@@ -25,8 +96,10 @@ export function loadVnextExecutionProfile(projectRoot) {
25
96
  || !profile.code_bootstrap || typeof profile.code_bootstrap.command !== "string" || !profile.code_bootstrap.command.trim()
26
97
  || typeof profile.code_bootstrap.policy_ref !== "string" || !profile.code_bootstrap.policy_ref.trim()
27
98
  || (profile.execution === undefined ? profile.schema_id === "dd-flow/project-execution@3" : !isExecutionRouting(profile.execution))) {
28
- throw new AppError("execution_profile_invalid", "Project execution profile does not match its supported dd-flow schema", 1, { file });
99
+ throw new AppError("execution_profile_invalid", "Project execution profile does not match its supported dd-flow schema", 1);
29
100
  }
101
+ if (profile.operational_decision !== undefined)
102
+ validateRunOperationalDecision(profile.operational_decision, profile);
30
103
  return { ...profile, stage_session_mode: profile.stage_session_mode ?? "same_session", code_review_mode: profile.code_review_mode ?? "off" };
31
104
  }
32
105
  export function executionProfilePath(projectRoot) { return path.join(projectRoot, relativePath); }
@@ -10,6 +10,7 @@ import { storageSessionId } from "./session-identity.js";
10
10
  import { reconcileUnfinishedChecks } from "./code-checks.js";
11
11
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
12
12
  import { stageWorkIds, stageGraphScope } from "./stage-work-graph.js";
13
+ import { eligibleCodexChildOverload } from "./codex-capacity.js";
13
14
  export const subagentCapacityKey = "runtime.subagent.available_slots";
14
15
  /** External concurrency is policy, never evidence of native child qualification. */
15
16
  export function getStageFanoutCapacity(context, input) {
@@ -143,6 +144,10 @@ export async function reconcileVnextFanout(context, input) {
143
144
  continue;
144
145
  }
145
146
  const link = active[0];
147
+ if (value.harness_id === "codex-desktop" && eligibleCodexChildOverload(child, value.parent_session_id)) {
148
+ issues.push({ code: "child_capacity_pending", work_id: link.work_id, session_id: child.session_id });
149
+ continue;
150
+ }
146
151
  const pendingCheck = () => context.db.get("SELECT 1 FROM check_receipts WHERE project_id = ? AND run_id = ? AND work_id = ? AND status = 'running' LIMIT 1", [project.id, input.runId, link.work_id]);
147
152
  if (pendingCheck()) {
148
153
  const run = context.db.get("SELECT workspace_root, run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, input.runId]);
@@ -18,12 +18,13 @@ import { acceptStageReport, prepareStageReport, publishStageReport } from "./sta
18
18
  import { bindStageCoordinatorWork, createChildWork, failWork, finishFanInWork, finishWork, refreshRunWorkProjection, startStageCoordinatorWork } from "./work-registry.js";
19
19
  import { addVnextCodeRepair } from "./vnext-code.js";
20
20
  import { checkReceiptEvidenceValid, checkReceipts, effectiveCheckDeclarations, mergeCheckProfileHash, readCodeCheckProfile, runCodeChecks, validateCheckDeclaration, validateFailedCheckRetry, workspaceChangedPaths, workspaceFingerprint } from "./code-checks.js";
21
- import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
21
+ import { currentStagePrompt, applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
22
22
  import { ensureLaneWorkspace, prepareLaneWorkspace, heartbeatLaneLock, releaseLaneLock, waitAcquireLaneLock } from "./lanes.js";
23
23
  import { assertRunMutationAllowed } from "./run-recovery.js";
24
24
  import { assertStageStartHookEvent } from "./hooks.js";
25
25
  import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
26
26
  import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix, publishVerificationMatrix } from "./verification-matrix.js";
27
+ import { stageSuccessor } from "./vnext-contracts.js";
27
28
  const stage = "merge";
28
29
  const stageDir = vnextStageDirectory(stage);
29
30
  export function isVnextMergeRun(context, input) {
@@ -176,11 +177,11 @@ export async function startVnextMerge(context, input, prepared = prepareVnextMer
176
177
  const prompt = existingPrompt ?? `${mergePrompt(context, { run, request, resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, run.run_root, "MERGE source/target verification and residual-risk assessment")}\n`;
177
178
  if (existingPrompt === null)
178
179
  fs.writeFileSync(promptPath, prompt);
179
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
180
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
180
181
  const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" };
181
182
  const attachment = alreadyAttached ? null : prepareFlowRunStageAttachment(context, attachmentInput);
182
183
  commitVerificationMatrixStart(context, prepared.matrix, () => attachment ? attachFlowRunStage(context, attachmentInput, attachment) : undefined);
183
- return startPacket(context, run, request, binding, promptPath, resultPath, prompt, projectRoot, checks, externalContext);
184
+ return startPacket(context, run, request, binding, promptPath, resultPath, projectRoot, checks, externalContext);
184
185
  }
185
186
  const current = requireRequest(context, request.merge_request_id);
186
187
  const targetHead = gitValue(current.target_workspace, ["rev-parse", current.target_branch], true);
@@ -196,13 +197,13 @@ export async function startVnextMerge(context, input, prepared = prepareVnextMer
196
197
  const started = startStageCoordinatorWork(context, { workId: current.executor_work_id, hookEventId: input.hookEventId, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
197
198
  const prompt = `${mergePrompt(context, { run, request: requireRequest(context, current.merge_request_id), resultPath, projectRoot }, checks, settings)}\n${stageLifecycleInstruction}\n${verificationMatrixInstruction(prepared.matrix?.metadata ?? null, run.run_root, "MERGE source/target verification and residual-risk assessment")}\n`;
198
199
  fs.writeFileSync(promptPath, prompt);
199
- const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
200
+ const externalContext = applyExternalStageContext({ context, stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
200
201
  context.db.run("UPDATE work_sessions SET prompt_path = ?, result_path = ?, updated_at = ? WHERE work_id = ? AND status = 'running'", [promptPath, resultPath, context.now(), current.executor_work_id]);
201
202
  const attachmentInput = { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@2" };
202
203
  const attachment = alreadyAttached ? null : prepareFlowRunStageAttachment(context, attachmentInput);
203
204
  commitVerificationMatrixStart(context, prepared.matrix, () => attachment ? attachFlowRunStage(context, attachmentInput, attachment) : undefined);
204
205
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "merge_started", merge_request_id: current.merge_request_id, work_id: current.executor_work_id, target_head: targetHead });
205
- return startPacket(context, run, current, started, promptPath, resultPath, prompt, projectRoot, checks, externalContext);
206
+ return startPacket(context, run, current, started, promptPath, resultPath, projectRoot, checks, externalContext);
206
207
  }
207
208
  catch (error) {
208
209
  const latest = requireRequest(context, request.merge_request_id);
@@ -216,8 +217,8 @@ export async function startVnextMerge(context, input, prepared = prepareVnextMer
216
217
  throw error;
217
218
  }
218
219
  }
219
- function startPacket(context, run, request, binding, promptPath, resultPath, prompt, projectRoot, checks, externalContext) {
220
- return { ok: true, run_id: run.id, stage, merge_request_id: request.merge_request_id, work_id: request.executor_work_id, session_binding: binding.session_binding ?? binding, prompt_path: promptPath, result_path: resultPath, worker_prompt_markdown: prompt, ...(externalContext ? { external_context: externalContext } : {}), queue: queueStatus(context, request), effective_checks: checks, next: { apply_command: applyCommand(context, request, projectRoot), finish_command: finishCommand(context, run.id, request, projectRoot) } };
220
+ function startPacket(context, run, request, binding, promptPath, resultPath, projectRoot, checks, externalContext) {
221
+ return { ok: true, run_id: run.id, stage, merge_request_id: request.merge_request_id, work_id: request.executor_work_id, session_binding: binding.session_binding ?? binding, result_path: resultPath, ...currentStagePrompt(context, promptPath), ...(externalContext ? { external_context: externalContext } : {}), queue: queueStatus(context, request), effective_checks: checks, next: { apply_command: applyCommand(context, request, projectRoot), finish_command: finishCommand(context, run.id, request, projectRoot) } };
221
222
  }
222
223
  export function applyVnextMerge(context, input) {
223
224
  const existing = requireRequest(context, input.requestId);
@@ -470,7 +471,7 @@ export async function repairVnextMerge(context, input) {
470
471
  if (request.project_id === project.id && request.status === "superseded" && errorCode(request) === "merge_source_repair") {
471
472
  const saved = JSON.parse(request.last_error_json);
472
473
  releaseMergeLane(context, projectRoot, request, `MERGE ${request.merge_request_id} moved to source repair`);
473
- return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: { repair_work_id: saved.repair_work_id }, next: { kind: "start_stage", stage: "code", command: managedLifecycleCommand(context, runtimeCommand(context, ["stage", "start", request.run_id, "--stage", "code", "--project-root", projectRoot, "--json", "--progress-jsonl"])) } };
474
+ return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: { repair_work_id: saved.repair_work_id }, next: stageSuccessor(context, { projectRoot, runId: request.run_id, stage: "code", command: runtimeCommand(context, ["stage", "start", request.run_id, "--stage", "code", "--project-root", projectRoot, "--json", "--progress-jsonl"]) }) };
474
475
  }
475
476
  if (request.project_id !== project.id || request.status !== "action_required" || errorCode(request) !== "merge_gate_failed") {
476
477
  throw new AppError("invalid_merge_repair_state", "MERGE source repair requires an action-required failed integration gate", 2, { merge_request_id: input.requestId, status: request.status, code: errorCode(request) });
@@ -494,7 +495,7 @@ export async function repairVnextMerge(context, input) {
494
495
  context.db.run("UPDATE merge_requests SET status = 'superseded', checkpoint = 'source_repair_required', last_error_json = ?, completed_at = ?, updated_at = ? WHERE merge_request_id = ?", [JSON.stringify({ code: "merge_source_repair", replacement_pending: true, failed_receipt_ids: failed, repair_work_id: repair.repair_work_id }), now, now, request.merge_request_id]);
495
496
  releaseMergeLane(context, projectRoot, request, `MERGE ${request.merge_request_id} moved to source repair`);
496
497
  appendFlowRunTimelineEvent(context, project.id, request.run_id, { type: "merge_source_repair_created", merge_request_id: request.merge_request_id, repair_work_id: repair.repair_work_id, cycle: prepared.cycle, failed_receipt_ids: failed });
497
- return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: repair, next: { kind: "start_stage", stage: "code", command: managedLifecycleCommand(context, runtimeCommand(context, ["stage", "start", request.run_id, "--stage", "code", "--project-root", projectRoot, "--json", "--progress-jsonl"])) } };
498
+ return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: repair, next: stageSuccessor(context, { projectRoot, runId: request.run_id, stage: "code", command: runtimeCommand(context, ["stage", "start", request.run_id, "--stage", "code", "--project-root", projectRoot, "--json", "--progress-jsonl"]) }) };
498
499
  }
499
500
  function mergePrompt(context, input, checks, settings) { const pause = stagePauseCommand(context, { runId: input.run.id, stage: "merge", workId: input.request.executor_work_id, projectRoot: input.projectRoot }); const repair = repairCommand(context, input.request, input.projectRoot); return ["<stage_identity>", `- RUN: ${input.run.id}`, `- MERGE request: ${input.request.merge_request_id}`, `- Work: ${input.request.executor_work_id}`, "- stage: merge", "</stage_identity>", "", "<trusted_runtime_context>", `- integration workspace: ${input.request.target_workspace}`, `- source workspace: ${input.request.source_workspace}`, `- frozen source commit: ${input.request.source_commit}`, `- target branch: ${input.request.target_branch}`, `- execution target baseline: ${input.request.execution_target_head}`, `- queue route: ${input.request.execution_route}`, `- delivery: ${JSON.stringify(settings.merge_delivery)}`, `- cleanup: ${JSON.stringify(settings.merge_cleanup)}`, "These facts and the acquired project integration lane were established by dd-flow. Do not repeat discovery and do not run git merge/rebase/squash yourself.", "</trusted_runtime_context>", "", "<effective_merge_gate>", ...checks.map((check) => `- ${check.canonical_ref ?? check.id}: ${check.command} — ${check.purpose}`), "</effective_merge_gate>", "", "<execution_contract>", `1. Run this exact standalone command first: ${applyCommand(context, input.request, input.projectRoot)}`, "2. If it reports conflicts, resolve only the actual unmerged paths in the integration workspace. Do not repeat merge apply and do not edit product code, tests, documentation or configuration merely to make a gate pass.", `3. Write the compact semantic result to ${input.resultPath}:`, "```json", JSON.stringify({ schema_id: "dd-flow/merge-result@1", outcome: "completed", summary: "What was integrated.", conflict_resolution: "How material conflicts were resolved, or empty when none.", verification_summary: "Why the integrated result is ready for deterministic checks.", residual_risks: [] }, null, 2), "```", `4. Finish with this exact standalone command and wait for all progress: ${finishCommand(context, input.run.id, input.request, input.projectRoot)}`, `If a gate fails, read its receipt and logs and determine the cause. For a product defect use ${repair}; it restores the target baseline and opens CODE → independent CODE-REVIEW → replacement MRG. For an environment failure restore the declared environment in this MERGE and write the returned recovery JSON file and use continuation.command_after_input. The CLI reruns the real gate and retains the old receipt. Never edit product code in the integration target to make a gate pass.`, "If a material conflict has no reasonable answer in accepted evidence, pause this same Work by writing the question file and then running the separate standalone command below, ask the returned user_message, then use the exact resume command returned by CLI:", "```sh", stagePauseCommandTemplate(pause), "```", "</execution_contract>", ""].join("\n"); }
500
501
  function mergeReport(context, run, request, semantic, receipts) {