@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.
- package/CHANGELOG.md +16 -0
- package/README.md +10 -1
- package/dist/build-info.json +7 -7
- package/dist/cli/argument-values.js +38 -0
- package/dist/cli/command-inputs.js +4 -3
- package/dist/cli/help.js +1 -0
- package/dist/cli/hook-ingress.js +1 -1
- package/dist/cli/run-cli.js +126 -193
- package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
- package/dist/cli/runtime-maintenance-ingress.js +51 -0
- package/dist/cli/runtime-process.js +128 -0
- package/dist/cli.js +8 -5
- package/dist/harness-runtime/bin/dd-agy.mjs +3 -2
- package/dist/harness-runtime/bin/dd-codex.mjs +3 -2
- package/dist/harness-runtime/bin/dd-droid.mjs +3 -2
- package/dist/harness-runtime/bin/dd-grok.mjs +16 -6
- package/dist/harness-runtime/bin/dd-opencode.mjs +3 -2
- package/dist/harness-runtime/bin/dd-zcode.mjs +20 -16
- package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
- package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +60 -13
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +151 -72
- package/dist/harness-runtime/lib/dd-agy.mjs +11 -23
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +9 -14
- package/dist/harness-runtime/lib/dd-codex.mjs +252 -80
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +14 -19
- package/dist/harness-runtime/lib/dd-droid.mjs +117 -31
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +56 -30
- package/dist/harness-runtime/lib/dd-grok.mjs +15 -13
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +18 -27
- package/dist/harness-runtime/lib/dd-opencode.mjs +44 -14
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +56 -53
- package/dist/harness-runtime/lib/dd-zcode.mjs +200 -111
- package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
- package/dist/harness-runtime/lib/lease-renewal.mjs +23 -0
- package/dist/harness-runtime/lib/lifecycle-command.d.mts +3 -3
- package/dist/harness-runtime/lib/lifecycle-command.mjs +74 -20
- package/dist/harness-runtime/lib/managed-daemon.d.mts +10 -1
- package/dist/harness-runtime/lib/managed-daemon.mjs +228 -40
- package/dist/harness-runtime/lib/native-children.d.mts +13 -0
- package/dist/harness-runtime/lib/native-children.mjs +78 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +4 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +115 -11
- package/dist/harness-runtime/lib/native-streams.mjs +45 -0
- package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +73 -17
- package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +105 -8
- package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
- package/dist/harness-runtime/lib/process-json.mjs +136 -38
- package/dist/harness-runtime/lib/tool-observations.d.mts +1 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +5 -2
- package/dist/harness-runtime/lib/zcode-usage.mjs +3 -2
- package/dist/runtime/context.js +8 -4
- package/dist/schemas/run-control-receipt.schema.json +19 -0
- package/dist/services/code-checks.js +79 -9
- package/dist/services/codex-capacity.js +117 -41
- package/dist/services/codex-hook-delivery.js +2 -2
- package/dist/services/compatibility-preflight.js +4 -1
- package/dist/services/controller-fanout.js +9 -76
- package/dist/services/engines.js +70 -20
- package/dist/services/eval-snapshots.js +179 -40
- package/dist/services/external-work-launch.js +13 -8
- package/dist/services/harness-adapter.js +175 -11
- package/dist/services/hooks.js +65 -21
- package/dist/services/lifecycle-invocations.js +404 -32
- package/dist/services/managed-lease-monitor.js +128 -0
- package/dist/services/managed-processes.js +103 -47
- package/dist/services/merge-server.js +58 -21
- package/dist/services/recovery-observation-budget.js +67 -7
- package/dist/services/recovery-snapshot-database.js +7 -4
- package/dist/services/run-control-receipt.js +1 -1
- package/dist/services/run-control-worker.js +177 -118
- package/dist/services/run-control.js +99 -8
- package/dist/services/run-controller-adapter.js +169 -46
- package/dist/services/run-controller-capture.js +14 -2
- package/dist/services/run-controller-process.js +11 -4
- package/dist/services/run-controller-state.js +68 -7
- package/dist/services/run-controller.js +166 -94
- package/dist/services/run-fork.js +8 -3
- package/dist/services/run-recovery-guard.js +75 -0
- package/dist/services/run-recovery-runtime.js +1 -56
- package/dist/services/run-recovery.js +17 -13
- package/dist/services/runs.js +39 -6
- package/dist/services/runtime-budget.js +32 -15
- package/dist/services/runtime-scope-control.js +8 -0
- package/dist/services/runtime-scope-resume.js +30 -8
- package/dist/services/runtime-scope-worker.js +118 -73
- package/dist/services/runtime-service.js +6 -7
- package/dist/services/scope-writer-cleanup.js +3 -2
- package/dist/services/stage-context.js +135 -0
- package/dist/services/stage-lifecycle.js +3 -15
- package/dist/services/stage-pause.js +3 -2
- package/dist/services/usage.js +14 -3
- package/dist/services/vnext-code-review.js +10 -9
- package/dist/services/vnext-code.js +10 -8
- package/dist/services/vnext-contracts.js +10 -1
- package/dist/services/vnext-execution-profile.js +75 -2
- package/dist/services/vnext-fanout.js +5 -0
- package/dist/services/vnext-merge.js +10 -9
- package/dist/services/vnext-plan-review.js +21 -17
- package/dist/services/vnext-plan.js +9 -7
- package/dist/services/vnext-protocolize.js +15 -9
- package/dist/services/vnext-specify.js +6 -6
- package/dist/services/work-registry.js +34 -4
- package/dist/services/workspace-bootstrap.js +14 -3
- package/dist/shared/errors.js +11 -6
- package/dist/storage/agent-output.js +26 -0
- package/dist/storage/database.js +80 -21
- 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. */
|
package/dist/services/usage.js
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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: {
|
|
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,
|
|
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
|
-
|
|
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: {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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: {
|
|
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: {
|
|
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) {
|