@deksden-com/dd-flow-cli 0.9.0-beta.9 → 0.9.0-beta.91
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 +467 -0
- package/README.md +65 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +305 -0
- package/dist/cli/help.js +89 -13
- package/dist/cli/hook-ingress.js +82 -0
- package/dist/cli/input-preparation.js +116 -0
- package/dist/cli/run-cli.js +1421 -384
- package/dist/cli.js +6 -2
- package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
- package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
- package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
- package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
- package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
- package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
- package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
- package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
- package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
- package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
- package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
- package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
- package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
- package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
- package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
- package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
- package/dist/harness-runtime/lib/model-observations.mjs +77 -0
- package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
- package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
- package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
- package/dist/harness-runtime/lib/operation-context.mjs +5 -0
- package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
- package/dist/harness-runtime/lib/process-json.mjs +70 -0
- package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
- package/dist/harness-runtime/lib/runner-events.mjs +200 -0
- package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
- package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
- package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
- package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
- package/dist/schemas/agent-profile.schema.json +1 -1
- package/dist/schemas/code-review-result.schema.json +2 -2
- package/dist/schemas/code-work-batch.schema.json +4 -3
- package/dist/schemas/code-work-result.schema.json +4 -4
- package/dist/schemas/plan-review-result.schema.json +2 -2
- package/dist/schemas/run-control-receipt.schema.json +99 -0
- package/dist/schemas/run-control-request.schema.json +36 -0
- package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
- package/dist/services/canon.js +11 -8
- package/dist/services/cleanup.js +57 -13
- package/dist/services/cli-operation-classifier.js +22 -24
- package/dist/services/code-checks.js +543 -96
- package/dist/services/codex-hook-delivery.js +28 -0
- package/dist/services/command-context.js +80 -0
- package/dist/services/config.js +11 -8
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +132 -0
- package/dist/services/dashboard.js +103 -31
- package/dist/services/engines.js +121 -76
- package/dist/services/eval-snapshots.js +750 -73
- package/dist/services/execution-policy.js +170 -0
- package/dist/services/external-work-launch.js +110 -0
- package/dist/services/harness-adapter.js +147 -24
- package/dist/services/harness-config.js +2 -0
- package/dist/services/hooks.js +587 -299
- package/dist/services/lanes.js +61 -53
- package/dist/services/lifecycle-command.js +103 -17
- package/dist/services/lifecycle-contract.js +30 -0
- package/dist/services/lifecycle-invocations.js +1122 -0
- package/dist/services/managed-daemon-binding.js +38 -0
- package/dist/services/managed-processes.js +283 -27
- package/dist/services/merge-queue.js +179 -104
- package/dist/services/merge-server.js +73 -28
- package/dist/services/migrations.js +14 -9
- package/dist/services/native-daemon-history.js +49 -0
- package/dist/services/native-session-control.js +39 -0
- package/dist/services/plan-runtime.js +19 -8
- package/dist/services/plans.js +28 -25
- package/dist/services/projects.js +18 -5
- package/dist/services/prompts.js +23 -16
- package/dist/services/protocols.js +64 -20
- package/dist/services/recovery-observation-budget.js +61 -0
- package/dist/services/recovery-snapshot-database.js +107 -0
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-control-receipt.js +56 -0
- package/dist/services/run-control-worker.js +363 -0
- package/dist/services/run-control.js +876 -0
- package/dist/services/run-controller-adapter.js +222 -0
- package/dist/services/run-controller-capture.js +134 -0
- package/dist/services/run-controller-process.js +189 -0
- package/dist/services/run-controller-recovery.js +222 -0
- package/dist/services/run-controller-state.js +36 -0
- package/dist/services/run-controller.js +793 -0
- package/dist/services/run-engine-bindings.js +1 -1
- package/dist/services/run-fork.js +138 -0
- package/dist/services/run-observations.js +112 -0
- package/dist/services/run-recovery-runtime.js +69 -0
- package/dist/services/run-recovery.js +343 -0
- package/dist/services/runs.js +368 -74
- package/dist/services/runtime-budget.js +434 -0
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/runtime-scope-capture.js +50 -0
- package/dist/services/runtime-scope-control.js +450 -0
- package/dist/services/runtime-scope-resume.js +565 -0
- package/dist/services/runtime-scope-stop.js +99 -0
- package/dist/services/runtime-scope-worker.js +243 -0
- package/dist/services/runtime-service.js +97 -0
- package/dist/services/schema-validation.js +10 -8
- package/dist/services/sessions.js +35 -69
- package/dist/services/stage-blocker.js +17 -7
- package/dist/services/stage-context.js +44 -18
- package/dist/services/stage-lifecycle.js +99 -102
- package/dist/services/stage-pause.js +84 -55
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/usage.js +37 -93
- package/dist/services/vnext-code-review.js +195 -88
- package/dist/services/vnext-code.js +285 -252
- package/dist/services/vnext-execution-profile.js +5 -3
- package/dist/services/vnext-fanout.js +160 -17
- package/dist/services/vnext-merge.js +291 -104
- package/dist/services/vnext-plan-review.js +139 -81
- package/dist/services/vnext-plan.js +138 -69
- package/dist/services/vnext-protocolize.js +129 -56
- package/dist/services/vnext-specify.js +88 -65
- package/dist/services/work-registry.js +716 -168
- package/dist/services/workspace-bootstrap.js +76 -0
- package/dist/services/worktrees.js +85 -35
- package/dist/shared/entity-references.js +8 -0
- package/dist/shared/errors.js +12 -0
- package/dist/storage/database.js +459 -32
- package/dist/storage/paths.js +17 -4
- package/dist/storage/work-references.js +23 -0
- package/dist/storage/writer-contract.js +77 -0
- package/dist/storage/writer-migration.js +102 -0
- package/package.json +24 -13
- package/tools/repair-paused-run-status.mjs +59 -0
|
@@ -1,43 +1,68 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
2
|
+
import { ensureAggregateRepair, ensureRepairIntent } from "./repair-intents.js";
|
|
3
|
+
import { runWorkspaceBootstrap } from "./workspace-bootstrap.js";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import { AppError } from "../shared/errors.js";
|
|
6
7
|
import { resolveProjectRoot } from "../storage/paths.js";
|
|
7
|
-
import { aggregateCheckDeclarations,
|
|
8
|
+
import { aggregateCheckDeclarations, effectiveCheckDeclarations, finalCodeCheckDeclarations, runCodeChecks, validateFailedCheckRetry, workspaceChangedPaths } from "./code-checks.js";
|
|
8
9
|
import { requireProjectByRoot } from "./projects.js";
|
|
9
|
-
import { appendFlowRunTimelineEvent, advanceFlowRun, attachFlowRunStage, completeFlowRunStage, completeFlowRun, getFlowRunVariables, gitFacts } from "./runs.js";
|
|
10
|
+
import { appendFlowRunTimelineEvent, advanceFlowRun, attachFlowRunStage, prepareFlowRunStageAttachment, completeFlowRunStage, completeFlowRun, withStageSettlement, getFlowRunVariables, gitFacts } from "./runs.js";
|
|
10
11
|
import { validateSchema } from "./schema-validation.js";
|
|
11
|
-
import {
|
|
12
|
+
import { runtimeCommand, checkRetryArgs } from "./runtime-command.js";
|
|
13
|
+
import { stageCycle, stageGraphScope } from "./stage-work-graph.js";
|
|
12
14
|
import { validateVnextCodeHandoff } from "./vnext-plan.js";
|
|
13
15
|
import { requireVnextWorkspaceRoute } from "./vnext-workspace-policy.js";
|
|
14
16
|
import { readVnextSpecifyResult } from "./vnext-specify.js";
|
|
15
|
-
import { addWorkBatch, bindStageCoordinatorWork, codeWorkGraph,
|
|
17
|
+
import { addWorkBatch, prepareWorkBatch, bindStageCoordinatorWork, codeWorkGraph, finishStageCoordinatorWork, refreshRunWorkProjection, validateWorkBatchFile, workStartCommand } from "./work-registry.js";
|
|
16
18
|
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
17
19
|
import { writeStageReport } from "./stage-report-renderer.js";
|
|
18
|
-
import {
|
|
20
|
+
import { prepareVnextMergeRequest } from "./vnext-merge.js";
|
|
19
21
|
import { assertStageStartHookEvent } from "./hooks.js";
|
|
20
|
-
import { applyExternalStageContext } from "./stage-context.js";
|
|
22
|
+
import { applyExternalStageContext, prepareExternalStageContext } from "./stage-context.js";
|
|
21
23
|
import { readFanoutDescriptor, writeFanoutDescriptor } from "./vnext-fanout.js";
|
|
24
|
+
import { assertRunMutationAllowed } from "./run-recovery.js";
|
|
25
|
+
import { prepareJsonSchema, prepareOutputFile } from "../cli/input-preparation.js";
|
|
22
26
|
const stage = "code";
|
|
23
27
|
const stageDir = vnextStageDirectory("code");
|
|
24
|
-
|
|
28
|
+
/** A controller prepares slow, deterministic project tooling before it asks an
|
|
29
|
+
* agent to enter CODE or a restored CODE_REVIEW. The subsequent lifecycle tool call remains the sole
|
|
30
|
+
* authority for attaching and binding the Stage. */
|
|
31
|
+
export async function prepareVnextCodeWorkspace(context, input) {
|
|
32
|
+
if (context.env.DD_FLOW_CODE_READINESS_OWNER !== input.ownerId) {
|
|
33
|
+
throw new AppError("code_readiness_owner_mismatch", "CODE readiness preparation must use its controller's frozen owner", 1, { owner_id: input.ownerId });
|
|
34
|
+
}
|
|
25
35
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
26
36
|
const project = requireProjectByRoot(context, projectRoot);
|
|
27
37
|
const run = requireRun(context, project.id, input.runId);
|
|
28
|
-
|
|
38
|
+
assertRunMutationAllowed(context, project.id, run.id);
|
|
39
|
+
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: requireHome(run), workspaceRoot: run.workspace_root, stage: input.stage ?? stage });
|
|
40
|
+
const root = path.join(requireHome(run), stageDir);
|
|
41
|
+
fs.mkdirSync(root, { recursive: true });
|
|
42
|
+
return ensureCodeWorkspaceReady(context, project.id, run, root, [], input.progress, { force: input.stage !== "code-review" });
|
|
43
|
+
}
|
|
44
|
+
export function prepareVnextCodeStart(context, input) {
|
|
45
|
+
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
46
|
+
const project = requireProjectByRoot(context, projectRoot);
|
|
47
|
+
const run = requireRun(context, project.id, input.runId);
|
|
48
|
+
assertRunMutationAllowed(context, project.id, run.id);
|
|
29
49
|
const home = requireHome(run);
|
|
30
50
|
requireVnextWorkspaceRoute({ projectRoot, runId: input.runId, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
31
51
|
const existingRoot = path.join(home, stageDir);
|
|
32
52
|
const existingPrompt = path.join(existingRoot, "stage-prompt.md");
|
|
33
53
|
const existingStage = stageStatus(run, stage);
|
|
54
|
+
const sourceRepairAttempt = isMergeSourceRepairAttempt(run);
|
|
55
|
+
const rootWork = requireRootWork(context, project.id, input.runId);
|
|
56
|
+
if (input.externalContext)
|
|
57
|
+
prepareExternalStageContext({ stageRoot: existingRoot, loaded: input.externalContext });
|
|
58
|
+
prepareOutputFile(existingPrompt);
|
|
59
|
+
prepareOutputFile(path.join(existingRoot, "fanout.json"));
|
|
60
|
+
const common = { projectRoot, project, run, home, root: existingRoot, promptPath: existingPrompt, rootWork, sourceRepairAttempt };
|
|
34
61
|
if (existingStage && fs.existsSync(existingPrompt)) {
|
|
35
|
-
|
|
36
|
-
const prompt = fs.readFileSync(existingPrompt, "utf8");
|
|
37
|
-
const rootWork = requireRootWork(context, project.id, input.runId);
|
|
38
|
-
const orchestration = readFanoutDescriptor(existingRoot) ?? writeFanoutDescriptor(existingRoot, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true });
|
|
39
|
-
return { ok: true, resumed: true, run_id: run.id, stage, stage_status: existingStage, prompt_path: existingPrompt, worker_prompt_markdown: prompt, ...(externalContext ? { external_context: externalContext } : {}), orchestration, graph: codeWorkGraph(context, project.id, run.id), next: { refresh_graph_command: `${flowCommand(context)} work ls --run ${run.id} --ready --project-root ${JSON.stringify(projectRoot)} --json`, finish_command: finishCommand(context, input.runId, projectRoot, path.join(existingRoot, "code-verification.json")) } };
|
|
62
|
+
return { ...common, resumed: true, existingStage, prompt: fs.readFileSync(existingPrompt, "utf8"), orchestration: readFanoutDescriptor(existingRoot) };
|
|
40
63
|
}
|
|
64
|
+
if (!sourceRepairAttempt)
|
|
65
|
+
prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/stage-report@1" });
|
|
41
66
|
const batch = path.join(home, "03-plan", "code-work-batch.json");
|
|
42
67
|
const handoffFailures = validateVnextCodeHandoff(context, {
|
|
43
68
|
projectRoot,
|
|
@@ -57,12 +82,25 @@ export async function startVnextCode(context, input) {
|
|
|
57
82
|
runRoot: home
|
|
58
83
|
});
|
|
59
84
|
validateWorkBatchFile(batch);
|
|
60
|
-
const
|
|
85
|
+
const template = fs.readFileSync(path.join(run.workspace_root, ".memory-bank", "dd-flow", "vnext", "code.md"), "utf8");
|
|
86
|
+
const variables = getFlowRunVariables(context, { projectRoot, runId: run.id });
|
|
87
|
+
return { ...common, resumed: false, template, capacity: variables.variables?.["runtime.subagent.available_slots"] };
|
|
88
|
+
}
|
|
89
|
+
export async function startVnextCode(context, input, prepared = prepareVnextCodeStart(context, input)) {
|
|
90
|
+
const { projectRoot, project, run, root, promptPath, rootWork, sourceRepairAttempt } = prepared;
|
|
91
|
+
assertRunMutationAllowed(context, project.id, run.id);
|
|
92
|
+
assertStageStartHookEvent(context, { projectId: project.id, eventKey: input.hookEventId, runId: run.id, stage, projectRoot, ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
|
|
93
|
+
if (prepared.resumed) {
|
|
94
|
+
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
95
|
+
bindStageCoordinatorWork(context, { workId: rootWork.work_id, hookEventId: input.hookEventId, stage, promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
|
|
96
|
+
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) });
|
|
97
|
+
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")) } };
|
|
98
|
+
}
|
|
61
99
|
fs.mkdirSync(root, { recursive: true });
|
|
62
100
|
const readiness = await ensureCodeWorkspaceReady(context, project.id, run, root, [], input.progress);
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const prompt = coordinatorPrompt(context, { projectRoot, run, root, rootWork, readiness });
|
|
101
|
+
// Graph rendering may issue managed child commands; it belongs to execution.
|
|
102
|
+
const graph = codeWorkGraph(context, project.id, run.id);
|
|
103
|
+
const prompt = coordinatorPrompt(context, { projectRoot, run, root, rootWork, readiness, template: prepared.template, capacity: prepared.capacity, graph });
|
|
66
104
|
fs.writeFileSync(promptPath, prompt);
|
|
67
105
|
const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
68
106
|
const binding = bindStageCoordinatorWork(context, {
|
|
@@ -73,16 +111,17 @@ export async function startVnextCode(context, input) {
|
|
|
73
111
|
resultPath: path.join(root, "stage-report.json"),
|
|
74
112
|
...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {})
|
|
75
113
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
114
|
+
if (!sourceRepairAttempt) {
|
|
115
|
+
attachFlowRunStage(context, {
|
|
116
|
+
projectRoot,
|
|
117
|
+
runId: input.runId,
|
|
118
|
+
stage,
|
|
119
|
+
dir: stageDir,
|
|
120
|
+
status: "running",
|
|
121
|
+
dataSchemaId: "dd-flow/stage-report@1"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: rootWork.work_id, dispatch: "none", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
|
|
86
125
|
appendFlowRunTimelineEvent(context, project.id, input.runId, { type: "code_started", work_id: rootWork.work_id, graph });
|
|
87
126
|
return {
|
|
88
127
|
ok: true,
|
|
@@ -96,7 +135,7 @@ export async function startVnextCode(context, input) {
|
|
|
96
135
|
graph,
|
|
97
136
|
orchestration,
|
|
98
137
|
readiness,
|
|
99
|
-
next: { refresh_graph_command:
|
|
138
|
+
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")) }
|
|
100
139
|
};
|
|
101
140
|
}
|
|
102
141
|
export async function finishVnextCode(context, input) {
|
|
@@ -105,8 +144,10 @@ export async function finishVnextCode(context, input) {
|
|
|
105
144
|
const run = requireRun(context, project.id, input.runId);
|
|
106
145
|
const home = requireHome(run);
|
|
107
146
|
const root = path.join(home, stageDir);
|
|
147
|
+
const generation = assertRunMutationAllowed(context, project.id, run.id);
|
|
108
148
|
requireVnextWorkspaceRoute({ projectRoot, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, stage });
|
|
109
149
|
if (stageStatus(run, stage) === "done" && fs.existsSync(path.join(root, "stage-report.json"))) {
|
|
150
|
+
resumeAcceptedCodeStage(context, run, projectRoot, stage, JSON.parse(fs.readFileSync(path.join(root, "stage-report.json"), "utf8")), nextAction(run, changedPathsFromReport(path.join(root, "stage-report.json"))));
|
|
110
151
|
return { ok: true, resumed: true, run_id: run.id, stage, outcome: "accepted", report_path: path.join(root, "stage-report.json"), next_action: nextAction(run, changedPathsFromReport(path.join(root, "stage-report.json"))) };
|
|
111
152
|
}
|
|
112
153
|
const handoffFailures = validateVnextCodeHandoff(context, {
|
|
@@ -135,7 +176,8 @@ export async function finishVnextCode(context, input) {
|
|
|
135
176
|
throw new AppError("obligation_coverage_incomplete", "CODE graph does not cover every accepted obligation", 2, { missing });
|
|
136
177
|
}
|
|
137
178
|
// Reject a malformed semantic receipt before running the expensive gate.
|
|
138
|
-
const verification = verificationForFinish(context, { file: input.verificationFile, projectRoot: run.workspace_root, runId: run.id, runRoot: home });
|
|
179
|
+
const verification = input.verification ?? verificationForFinish(context, { file: input.verificationFile, projectRoot: run.workspace_root, runId: run.id, runRoot: home });
|
|
180
|
+
validateSchema({ schemaName: "code-verification", file: input.verificationFile, data: verification, projectRoot: run.workspace_root, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
|
|
139
181
|
if (verification.verdict === "blocked") {
|
|
140
182
|
return { ok: true, run_id: run.id, stage, outcome: "blocked", verification, instruction: "CODE remains running. Resolve the stated external or user-input blocker in this same coordinator session, update code-verification.json, then invoke this same stage finish command again." };
|
|
141
183
|
}
|
|
@@ -149,58 +191,44 @@ export async function finishVnextCode(context, input) {
|
|
|
149
191
|
originWorkIds: works.filter((work) => work.status === "completed").map((work) => work.work_id),
|
|
150
192
|
semanticUnresolved: verification.unresolved.length ? verification.unresolved : [verification.summary],
|
|
151
193
|
verificationPath: input.verificationFile,
|
|
194
|
+
verification,
|
|
152
195
|
objective: verification.summary
|
|
153
196
|
});
|
|
154
|
-
return { ok: true, run_id: run.id, stage, outcome: "repair_required", verification, repair, instruction: "The semantic verification is not accepted.
|
|
197
|
+
return { ok: true, run_id: run.id, stage, outcome: "repair_required", verification, repair, instruction: "The semantic verification is not accepted. The repair Work is already registered: do not invoke its start_command in this coordinator Session. End this Turn so the controller can launch it in a fresh child Session. After that Work completes, update code-verification.json and invoke this same stage finish command again." };
|
|
155
198
|
}
|
|
156
199
|
const checks = finalCodeCheckDeclarations(run.workspace_root, works.flatMap((work) => packet(work)?.checks ?? []));
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
receipts = reusableAggregateGateReceipts(context, { projectId: project.id, runId: run.id, workspaceRoot: run.workspace_root, checks }) ?? await runCodeChecks(context, {
|
|
167
|
-
projectId: project.id,
|
|
168
|
-
runId: run.id,
|
|
169
|
-
runHome: home,
|
|
170
|
-
workspaceRoot: run.workspace_root,
|
|
171
|
-
scope: "aggregate",
|
|
172
|
-
checks,
|
|
173
|
-
...(input.progress ? { progress: input.progress } : {})
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
finally {
|
|
177
|
-
release();
|
|
178
|
-
}
|
|
179
|
-
}
|
|
200
|
+
if (input.retryCheckId) {
|
|
201
|
+
validateFailedCheckRetry(context, { projectId: project.id, runId: run.id, receiptId: input.retryCheckId, declarations: checks, reason: input.retryReason });
|
|
202
|
+
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "check_retry_requested", check_receipt_id: input.retryCheckId, stage, reason: input.retryReason ?? "operator retry" });
|
|
203
|
+
}
|
|
204
|
+
const receipts = await runCodeChecks(context, {
|
|
205
|
+
projectId: project.id, runId: run.id, runHome: home, workspaceRoot: run.workspace_root, scope: "aggregate", checks,
|
|
206
|
+
...(input.retryCheckId ? { retryReceiptId: input.retryCheckId, retryReason: input.retryReason } : {}),
|
|
207
|
+
...(input.progress ? { progress: input.progress } : {})
|
|
208
|
+
});
|
|
180
209
|
const failed = receipts.filter((receipt) => receipt.status !== "passed");
|
|
181
210
|
if (failed.length) {
|
|
211
|
+
const continuation = registerAggregateCodeRepair(context, { projectRoot, runId: run.id, stage, failures: failed, checks, generation });
|
|
182
212
|
throw new AppError("code_gate_failed", "CODE remains running because the aggregate project gate failed", 2, {
|
|
183
213
|
failures: failed,
|
|
184
214
|
outcome: "repair_required",
|
|
185
215
|
retry_after_workspace_change: true,
|
|
186
216
|
workspace_fingerprint: failed[0].workspace_fingerprint,
|
|
187
|
-
|
|
217
|
+
continuation,
|
|
218
|
+
instruction: "Repair Work is already registered. End this coordinator Turn; the controller will launch a fresh child. After it completes, update semantic verification and finish the Stage."
|
|
188
219
|
});
|
|
189
220
|
}
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
const projectedVerification = verificationProjection(works, finalReceipts, { historicalReceipts: allReceipts, runId: run.id, runHome: home });
|
|
221
|
+
const finalReceipts = receipts;
|
|
222
|
+
const projectedVerification = verificationProjection(works, finalReceipts, { historicalReceipts: finalReceipts, runId: run.id, runHome: home });
|
|
193
223
|
const unresolvedAcceptance = (projectedVerification.acceptance ?? []).filter((item) => item.status === "unresolved");
|
|
194
224
|
if (unresolvedAcceptance.length)
|
|
195
225
|
throw new AppError("code_acceptance_unresolved", "CODE cannot finish until every due acceptance criterion has current checks and evidence", 2, { unresolved: unresolvedAcceptance });
|
|
196
226
|
const now = context.now();
|
|
197
227
|
const timing = stageTiming(home, stage, now);
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
if (missingReportedPaths.length)
|
|
203
|
-
throw new AppError("changed_path_not_materialized", "CODE Work reported paths that are not changed in the accepted workspace", 2, { paths: missingReportedPaths });
|
|
228
|
+
// Work result paths are provenance, not a mutable filesystem oracle. The
|
|
229
|
+
// stage reports one shared Git observation; ignored machine repairs are
|
|
230
|
+
// rejected before a Work can become completed.
|
|
231
|
+
const changedPaths = workspaceChangedPaths(run.workspace_root);
|
|
204
232
|
const next = nextAction(run, changedPaths);
|
|
205
233
|
const report = {
|
|
206
234
|
schema_id: "dd-flow/stage-report@1",
|
|
@@ -230,8 +258,8 @@ export async function finishVnextCode(context, input) {
|
|
|
230
258
|
wall_clock_ms: timing.wall_clock_ms,
|
|
231
259
|
git: gitFacts(run.workspace_root),
|
|
232
260
|
graph: codeWorkGraph(context, project.id, run.id),
|
|
233
|
-
session_stats_command:
|
|
234
|
-
usage_stats_command:
|
|
261
|
+
session_stats_command: runtimeCommand(context, ["stat", "run", "sessions", "ls", "--run", run.id, "--project-root", projectRoot, "--json"]),
|
|
262
|
+
usage_stats_command: runtimeCommand(context, ["stat", "usage", "--run", run.id, "--project-root", projectRoot, "--json"])
|
|
235
263
|
},
|
|
236
264
|
artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html", summary: "stage-report.md" },
|
|
237
265
|
validation: { permission_scope: "known_targets_only", memory_bank_scope: "changed_files_and_links_only", status: "passed" }
|
|
@@ -239,31 +267,41 @@ export async function finishVnextCode(context, input) {
|
|
|
239
267
|
// Creating the MERGE request is part of accepting this terminal handoff.
|
|
240
268
|
// Do it before materialising a terminal stage report or changing RUN state,
|
|
241
269
|
// so an invalid merge policy leaves CODE safely running and retryable.
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
status
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
270
|
+
let mergeRequest = null;
|
|
271
|
+
assertRunMutationAllowed(context, project.id, run.id, generation);
|
|
272
|
+
const prepareMerge = next === "start_merge" ? prepareVnextMergeRequest(context, { projectRoot, runId: run.id, acceptedPaths: changedPaths }) : null;
|
|
273
|
+
withStageSettlement(context, () => {
|
|
274
|
+
assertRunMutationAllowed(context, project.id, run.id, generation);
|
|
275
|
+
const currentWorks = codeWorks(context, project.id, run.id);
|
|
276
|
+
if (currentWorks.length !== works.length || currentWorks.some(work => work.status !== "completed" || !works.some(accepted => accepted.work_id === work.work_id && accepted.result === work.result))) {
|
|
277
|
+
throw new AppError("code_graph_incomplete", "CODE results changed while the final checks were running", 2);
|
|
278
|
+
}
|
|
279
|
+
mergeRequest = prepareMerge?.() ?? null;
|
|
280
|
+
writeReport(root, report);
|
|
281
|
+
completeFlowRunStage(context, {
|
|
282
|
+
projectRoot,
|
|
283
|
+
runId: run.id,
|
|
284
|
+
stage,
|
|
285
|
+
status: "done",
|
|
286
|
+
data: "stage-report.json",
|
|
287
|
+
dataSchemaId: "dd-flow/stage-report@1",
|
|
288
|
+
report: "stage-report.md",
|
|
289
|
+
stageReport: "stage-report.html"
|
|
290
|
+
});
|
|
291
|
+
const rootWork = requireRootWork(context, project.id, run.id);
|
|
292
|
+
appendFlowRunTimelineEvent(context, project.id, run.id, {
|
|
293
|
+
type: "code_accepted",
|
|
294
|
+
work_id: rootWork.work_id,
|
|
295
|
+
checks: finalReceipts.map((receipt) => receipt.id)
|
|
296
|
+
});
|
|
297
|
+
if (next === "code_completed") {
|
|
298
|
+
finishStageCoordinatorWork(context, rootWork.work_id, JSON.stringify(report, null, 2));
|
|
299
|
+
completeFlowRun(context, { projectRoot, runId: run.id, status: "done", verdict: "code_completed", nextAction: undefined });
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
advanceFlowRun(context, { settlementStage: "code", projectRoot, runId: run.id, status: "running", verdict: "code_completed", nextAction: next });
|
|
303
|
+
}
|
|
259
304
|
});
|
|
260
|
-
if (next === "code_completed") {
|
|
261
|
-
await finishWork(context, rootWork.work_id, JSON.stringify(report, null, 2));
|
|
262
|
-
completeFlowRun(context, { projectRoot, runId: run.id, status: "done", verdict: "code_completed", nextAction: undefined });
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
advanceFlowRun(context, { projectRoot, runId: run.id, status: "running", verdict: "code_completed", nextAction: next });
|
|
266
|
-
}
|
|
267
305
|
return {
|
|
268
306
|
ok: true,
|
|
269
307
|
run_id: run.id,
|
|
@@ -271,19 +309,58 @@ export async function finishVnextCode(context, input) {
|
|
|
271
309
|
outcome: "accepted",
|
|
272
310
|
report_path: path.join(root, "stage-report.json"),
|
|
273
311
|
next_action: next,
|
|
274
|
-
...(next === "start_code_review" ? { next: { kind: "start_stage", stage: "code-review", command:
|
|
312
|
+
...(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"])) } } : {})
|
|
275
313
|
};
|
|
276
314
|
}
|
|
277
|
-
export function
|
|
315
|
+
export function registerAggregateCodeRepair(context, input) {
|
|
316
|
+
const project = requireProjectByRoot(context, input.projectRoot);
|
|
317
|
+
const origins = codeWorks(context, project.id, input.runId).filter(work => !packet(work)?.repair && work.status === "completed");
|
|
318
|
+
const failedDeclarations = new Set(input.failures.flatMap(receipt => [receipt.declaration_id, ...receipt.check_refs]));
|
|
319
|
+
const objective = ["Investigate and repair all retained aggregate check failures while preserving accepted requirements and non-goals. If evidence proves an external blocker or a contradiction, report it; do not invent a product change.", ...input.failures.map(receipt => `Receipt: ${receipt.receipt_path}\nstdout: ${receipt.stdout_path}\nstderr: ${receipt.stderr_path}`)].join("\n\n");
|
|
320
|
+
try {
|
|
321
|
+
return ensureAggregateRepair(context, { projectId: project.id, runId: input.runId, stage: input.stage, failures: input.failures, origins: origins.map(work => work.work_id), ...(input.generation !== undefined ? { generation: input.generation } : {}) }, () => String(addVnextCodeRepair(context, { registrationOwned: true, projectRoot: input.projectRoot, runId: input.runId, stage: input.stage, ...(input.stage === "code-review" ? { reviewCycle: stageCycle(context, project.id, input.runId, input.stage) } : {}), checkReceiptId: input.failures[0].id, originWorkIds: origins.map(work => work.work_id), objective, reviewChecks: input.checks.filter(check => failedDeclarations.has(check.id) || failedDeclarations.has(check.canonical_ref ?? "")) }).repair_work_id));
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
if (!(error instanceof AppError))
|
|
325
|
+
throw error;
|
|
326
|
+
throw new AppError(error.code, error.message, error.exitCode, { ...error.details, stage: input.stage, run_id: input.runId, failures: input.failures, cause: { code: input.stage === "code" ? "code_gate_failed" : "code_review_gate_failed", message: "Aggregate checks failed; repair registration was rejected" } });
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
export function prepareVnextCodeRepair(context, input) {
|
|
278
330
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
279
331
|
const project = requireProjectByRoot(context, projectRoot);
|
|
280
332
|
const run = requireRun(context, project.id, input.runId);
|
|
281
333
|
const home = requireHome(run);
|
|
282
|
-
if (
|
|
334
|
+
if (input.reviewCycle) {
|
|
335
|
+
const previous = codeWorks(context, project.id, run.id).find((work) => packet(work)?.review_cycle === input.reviewCycle && (input.checkReceiptId ? packet(work)?.repair?.check_receipt_id === input.checkReceiptId : Boolean(packet(work)?.repair?.review_findings?.length)));
|
|
336
|
+
if (previous)
|
|
337
|
+
return { kind: "replay", project, run, previous };
|
|
338
|
+
}
|
|
339
|
+
if (typeof input.objective !== "string" || !input.objective.trim())
|
|
283
340
|
throw new AppError("validation", "Repair objective must not be empty", 2);
|
|
284
|
-
if (
|
|
341
|
+
if (input.semanticUnresolved !== undefined && (!Array.isArray(input.semanticUnresolved) || input.semanticUnresolved.some(value => typeof value !== "string" || !value.trim())))
|
|
342
|
+
throw new AppError("validation", "Semantic unresolved items must be nonempty strings", 2);
|
|
343
|
+
if (input.reviewFindings !== undefined && !Array.isArray(input.reviewFindings))
|
|
344
|
+
throw new AppError("validation", "Review findings must be an array", 2);
|
|
345
|
+
if (input.reviewChecks !== undefined && !Array.isArray(input.reviewChecks))
|
|
346
|
+
throw new AppError("validation", "Review checks must be an array", 2);
|
|
347
|
+
if (!Array.isArray(input.originWorkIds) || !input.originWorkIds.length || input.originWorkIds.some(id => typeof id !== "string" || !id.trim()))
|
|
348
|
+
throw new AppError("validation", "Repair requires at least one origin Work", 2);
|
|
349
|
+
if (!input.checkReceiptId && !input.reviewFindings?.length && !input.semanticUnresolved?.length) {
|
|
285
350
|
throw new AppError("validation", "Repair needs failed check evidence, a CODE-REVIEW finding, or an unresolved CODE verification", 2);
|
|
286
351
|
}
|
|
352
|
+
if ([Boolean(input.checkReceiptId), Boolean(input.reviewFindings?.length), Boolean(input.semanticUnresolved?.length)].filter(Boolean).length !== 1)
|
|
353
|
+
throw new AppError("validation", "Repair requires exactly one evidence source", 2);
|
|
354
|
+
if (input.semanticUnresolved?.length) {
|
|
355
|
+
if (!input.verificationPath)
|
|
356
|
+
throw new AppError("usage", "Semantic repair requires a verification file", 2);
|
|
357
|
+
const verification = input.verification ?? readVerification(context, { file: input.verificationPath, projectRoot: run.workspace_root, runId: run.id, runRoot: home });
|
|
358
|
+
if (input.verification !== undefined)
|
|
359
|
+
validateSchema({ schemaName: "code-verification", file: input.verificationPath, data: verification, projectRoot: run.workspace_root, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
|
|
360
|
+
const unresolved = verification.unresolved.length ? verification.unresolved : [verification.summary];
|
|
361
|
+
if (verification.verdict !== "needs_repair" || input.semanticUnresolved.some(value => !unresolved.includes(value)))
|
|
362
|
+
throw new AppError("validation", "Semantic repair must reference unresolved items from a needs_repair verification", 2);
|
|
363
|
+
}
|
|
287
364
|
const receipt = input.checkReceiptId ? context.db.get("SELECT id, declaration_id, run_id, command, status, receipt_path, stdout_path, stderr_path FROM check_receipts WHERE project_id = ? AND id = ?", [
|
|
288
365
|
project.id,
|
|
289
366
|
input.checkReceiptId
|
|
@@ -304,10 +381,10 @@ export function addVnextCodeRepair(context, input) {
|
|
|
304
381
|
// repair that fixes one failed aggregate check can make a second, already
|
|
305
382
|
// declared aggregate failure impossible to repair. The immutable source
|
|
306
383
|
// of repair eligibility is the original CODE graph.
|
|
307
|
-
const declaredChecks =
|
|
384
|
+
const declaredChecks = effectiveCheckDeclarations(run.workspace_root, codeWorks(context, project.id, run.id)
|
|
308
385
|
.map((work) => packet(work))
|
|
309
386
|
.filter((value) => !value.repair)
|
|
310
|
-
.flatMap((value) => value.checks));
|
|
387
|
+
.flatMap((value) => value.checks), ["work", "code", "readiness", "merge"]);
|
|
311
388
|
const failedCheck = receipt ? declaredChecks.find((check) => check.id === receipt.declaration_id) : undefined;
|
|
312
389
|
if (receipt && !failedCheck) {
|
|
313
390
|
throw new AppError("repair_check_declaration_missing", "Repair receipt is not backed by an accepted CODE check declaration", 2, {
|
|
@@ -316,7 +393,7 @@ export function addVnextCodeRepair(context, input) {
|
|
|
316
393
|
});
|
|
317
394
|
}
|
|
318
395
|
const semanticRepair = Boolean(input.semanticUnresolved?.length);
|
|
319
|
-
const key = input.
|
|
396
|
+
const key = input.reviewFindings?.length ? "code-review-repair" : semanticRepair ? "code-verification-repair" : "code-gate-repair";
|
|
320
397
|
const receiptWriteScope = receipt ? receiptRepairPaths(run.workspace_root, receipt) : [];
|
|
321
398
|
const repair = {
|
|
322
399
|
schema_id: "dd-flow/code-work-packet@5",
|
|
@@ -326,8 +403,12 @@ export function addVnextCodeRepair(context, input) {
|
|
|
326
403
|
repair: {
|
|
327
404
|
origin_work_ids: origins.map((work) => work.work_id),
|
|
328
405
|
...(receipt ? { check_receipt_id: receipt.id, failure_receipt_path: receipt.receipt_path } : {}),
|
|
329
|
-
...(input.
|
|
330
|
-
...(semanticRepair ? { semantic_unresolved: unique(input.semanticUnresolved ?? []), verification_path: input.verificationPath } : {})
|
|
406
|
+
...(input.reviewFindings?.length ? { review_findings: input.reviewFindings } : {}),
|
|
407
|
+
...(semanticRepair ? { semantic_unresolved: unique(input.semanticUnresolved ?? []), verification_path: input.verificationPath } : {}),
|
|
408
|
+
// `run_at` remains the accepted aggregate placement. This separate
|
|
409
|
+
// repair obligation is deliberately executed before this repair Work is
|
|
410
|
+
// accepted, so a local fix cannot close without re-proving its cause.
|
|
411
|
+
verification_check_refs: selectRepairChecks({ ...(failedCheck ? { failedCheck } : {}), ...(input.reviewChecks ? { reviewChecks: input.reviewChecks } : {}), ...(semanticRepair ? { semanticChecks: declaredChecks } : {}) })
|
|
331
412
|
},
|
|
332
413
|
task: input.objective,
|
|
333
414
|
semantic_spine: {
|
|
@@ -335,14 +416,14 @@ export function addVnextCodeRepair(context, input) {
|
|
|
335
416
|
component_responsibility: "Diagnose and repair the failed accepted CODE result without changing unrelated behavior.",
|
|
336
417
|
must_preserve: unique(invariantPackets.flatMap((value) => value.semantic_spine.must_preserve)),
|
|
337
418
|
non_goals: unique(invariantPackets.flatMap((value) => value.semantic_spine.non_goals)),
|
|
338
|
-
acceptance_contribution: receipt ? `Make failed check pass: ${receipt.command}` : semanticRepair ? `Resolve CODE verification gap(s): ${input.semanticUnresolved.join("; ")}` : `Resolve CODE-REVIEW finding(s): ${input.
|
|
419
|
+
acceptance_contribution: receipt ? `Make failed check pass: ${receipt.command}` : semanticRepair ? `Resolve CODE verification gap(s): ${input.semanticUnresolved.join("; ")}` : `Resolve CODE-REVIEW finding(s): ${input.reviewFindings.map((finding) => finding.finding_ref).join(", ")}`
|
|
339
420
|
},
|
|
340
421
|
requirements: uniqueBy(invariantPackets.flatMap((value) => value.requirements), (value) => value.id),
|
|
341
422
|
acceptance: uniqueBy(invariantPackets.flatMap((value) => value.acceptance), (value) => JSON.stringify(value)),
|
|
342
423
|
// A CODE-REVIEW repair changes delivered code/evidence, never the
|
|
343
424
|
// already accepted PLAN or its ownership declaration.
|
|
344
425
|
document_updates: [],
|
|
345
|
-
required_read: unique([...(receipt ? [receipt.receipt_path] : input.
|
|
426
|
+
required_read: unique([...(receipt ? [receipt.receipt_path] : input.reviewFindings?.flatMap((finding) => finding.evidence_refs) ?? []), ...(semanticRepair && input.verificationPath ? [input.verificationPath] : []), ...packets.flatMap((value) => value.required_read)]),
|
|
346
427
|
discovery_boundary: unique(packets.flatMap((value) => value.discovery_boundary)),
|
|
347
428
|
// These are collision-avoidance hints. Receipt paths enrich the coordinator
|
|
348
429
|
// picture but never restrict the repair's project-local edits.
|
|
@@ -357,35 +438,36 @@ export function addVnextCodeRepair(context, input) {
|
|
|
357
438
|
...(receipt ? ["Stop and report the blocker if the proposed repair contradicts an accepted requirement or non-goal."] : ["Do not modify accepted PLAN artifacts, code-work-batch.json, or review decisions. Stop and report if the finding cannot be repaired within accepted scope."])
|
|
358
439
|
]),
|
|
359
440
|
depends_on: origins.map((work) => work.work_id),
|
|
360
|
-
result_schema: "dd-flow/code-work-result@
|
|
441
|
+
result_schema: "dd-flow/code-work-result@3",
|
|
442
|
+
...(input.reviewCycle ? { review_cycle: input.reviewCycle } : {})
|
|
361
443
|
};
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
}
|
|
372
|
-
const id =
|
|
444
|
+
const parentWorkId = requireRootWork(context, project.id, run.id).work_id;
|
|
445
|
+
const items = prepareWorkBatch(context, { parentWorkId, value: { works: [repair] } });
|
|
446
|
+
return { kind: "create", project, run, input, parentWorkId, items, key, receipt };
|
|
447
|
+
}
|
|
448
|
+
export function addVnextCodeRepair(context, input, prepared = prepareVnextCodeRepair(context, input)) {
|
|
449
|
+
const { project, run } = prepared;
|
|
450
|
+
if (prepared.kind === "replay")
|
|
451
|
+
return { ok: true, run_id: run.id, repair_work_id: prepared.previous.work_id, start_command: workStartCommand(context, prepared.previous), reused: true };
|
|
452
|
+
const { parentWorkId, items, key, receipt } = prepared;
|
|
453
|
+
const create = () => String(addWorkBatch(context, { parentWorkId, prepared: items, stage: input.stage ?? (input.reviewCycle ? "code-review" : "code"), ...(input.reviewCycle ? { cycle: input.reviewCycle } : {}) }).work_ids[key]);
|
|
454
|
+
const id = input.registrationOwned ? create() : ensureRepairIntent(context, {
|
|
455
|
+
projectId: project.id, runId: run.id, stage: input.stage ?? (input.reviewCycle ? "code-review" : "code"),
|
|
456
|
+
causes: input.checkReceiptId ? { receipt: input.checkReceiptId, origins: [...input.originWorkIds].sort() } : input.reviewCycle ? { review_cycle: input.reviewCycle, findings: input.reviewFindings } : { verification: input.verification, unresolved: input.semanticUnresolved, origins: [...input.originWorkIds].sort() }
|
|
457
|
+
}, create).work_id;
|
|
373
458
|
const work = context.db.get("SELECT * FROM works WHERE work_id = ?", [id]);
|
|
374
459
|
appendFlowRunTimelineEvent(context, project.id, run.id, {
|
|
375
460
|
type: "code_repair_created",
|
|
376
461
|
work_id: id,
|
|
377
|
-
origin_work_ids: input.originWorkIds,
|
|
378
|
-
...(receipt ? { check_receipt_id: receipt.id } : input.
|
|
462
|
+
origin_work_ids: prepared.input.originWorkIds,
|
|
463
|
+
...(receipt ? { check_receipt_id: receipt.id } : prepared.input.reviewFindings?.length ? { review_finding_ids: prepared.input.reviewFindings.map((finding) => finding.finding_ref) } : { semantic_unresolved: prepared.input.semanticUnresolved })
|
|
379
464
|
});
|
|
380
465
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
381
466
|
return { ok: true, run_id: run.id, repair_work_id: id, start_command: workStartCommand(context, work) };
|
|
382
467
|
}
|
|
383
|
-
/** Retain the causal declaration for worker context; run_at
|
|
468
|
+
/** Retain the causal declaration for worker context; run_at still records its original gate. */
|
|
384
469
|
export function selectRepairChecks(input) {
|
|
385
|
-
|
|
386
|
-
// work-scoped entries, while retaining the aggregate declaration tells the
|
|
387
|
-
// worker exactly what will be rerun by the coordinator.
|
|
388
|
-
const candidates = input.failedCheck ? [input.failedCheck] : input.reviewChecks ?? input.semanticChecks ?? [];
|
|
470
|
+
const candidates = input.failedCheck ? [input.failedCheck, ...(input.reviewChecks ?? [])] : input.reviewChecks ?? input.semanticChecks ?? [];
|
|
389
471
|
const checks = candidates.map((check) => ({ ...check, purpose: `${input.semanticChecks ? "Prove the CODE verification repair" : "Prove the CODE-REVIEW repair"}: ${check.purpose}` }));
|
|
390
472
|
return uniqueBy(checks, (check) => check.id);
|
|
391
473
|
}
|
|
@@ -414,10 +496,7 @@ function receiptRepairPaths(workspaceRoot, receipt) {
|
|
|
414
496
|
return [...paths].sort();
|
|
415
497
|
}
|
|
416
498
|
function coordinatorPrompt(context, input) {
|
|
417
|
-
const graph
|
|
418
|
-
const variables = getFlowRunVariables(context, { projectRoot: input.projectRoot, runId: input.run.id });
|
|
419
|
-
const capacity = variables.variables?.["runtime.subagent.available_slots"];
|
|
420
|
-
const template = fs.readFileSync(path.join(input.run.workspace_root, ".memory-bank", "dd-flow", "vnext", "code.md"), "utf8");
|
|
499
|
+
const { graph, capacity, template } = input;
|
|
421
500
|
return [
|
|
422
501
|
"<stage_identity>",
|
|
423
502
|
`- RUN: ${input.run.id}`,
|
|
@@ -439,11 +518,11 @@ function coordinatorPrompt(context, input) {
|
|
|
439
518
|
"</code_graph>",
|
|
440
519
|
"",
|
|
441
520
|
"<execution_commands>",
|
|
442
|
-
"
|
|
443
|
-
"A quiet child is still running until the harness reports its turn completed, failed, cancelled or explicitly needs attention. An elapsed nominal wait, silence, or no new artifact is not an unresponsive-worker failure. Never interrupt, replace, relaunch, or stage-block a still-running child for that reason, even if an external controller asks. Long work finish and stage finish commands emit check progress on stderr. After you issue the exact CODE stage finish command, wait for that same command to return:
|
|
444
|
-
`A repairable engine, harness, or environment failure is not a user question.
|
|
521
|
+
"Do not choose a provider delegation tool from this stage prompt. At each work_fanout boundary, stop at the Work-graph boundary; the shared controller supplies the selected adapter's native tool, exact parameters, child task, and wait contract. It launches only entries listed in graph.ready and uses at most the qualified capacity. Every registered CODE Work runs in a fresh child Session, including a serial dependency chain. The coordinator launches those children only through the controller-supplied native delegation instruction; it must never invoke a Work start_command itself. Each child receives its complete packet from dd-flow and runs its own exact start_command. After a Work finishes, the controller uses the graph returned by work finish to launch newly ready Work. To refresh the parent graph yourself use the exact command: " + runtimeCommand(context, ["work", "ls", "--run", input.run.id, "--ready", "--project-root", input.projectRoot, "--json"]),
|
|
522
|
+
"A quiet child is still running until the harness reports its turn completed, failed, cancelled or explicitly needs attention. An elapsed nominal wait, silence, or no new artifact is not an unresponsive-worker failure. Never interrupt, replace, relaunch, or stage-block a still-running child for that reason, even if an external controller asks. Long work finish and stage finish commands emit check progress on stderr. After you issue the exact CODE stage finish command, wait for that same command to return once: a completed command with a non-zero exit and structured `code_gate_failed` output is its terminal result, not a reason to keep waiting. A repair_required continuation already registered the repair: end this Turn. Do not invoke that Work's start_command from the coordinator Session, inspect its PID, start a second finish command, or infer failure from quiet output. Close a disposable child only after its Work is accepted or explicitly failed/cancelled and the harness reports the turn settled.",
|
|
523
|
+
`A repairable engine, harness, or environment failure is not a user question. Write the evidence summary to ${path.join(requireHome(input.run), "works", input.rootWork.work_id, "block-summary.md")}, then record it with a separate command: ${runtimeCommand(context, ["stage", "block", input.run.id, "--stage", "code", "--work", input.rootWork.work_id, "--kind", "<engine|harness|environment>", "--code", "<stable-code>", "--summary-file", `@run/works/${input.rootWork.work_id}/block-summary.md`, "--retryable", "--project-root", input.projectRoot, "--json"])}. Choose kind and stable-code from evidence; this example is not an executable continuation. Repair it externally, then run the exact unblock_command returned by dd-flow and continue this same stage.`,
|
|
445
524
|
`When every CODE and repair Work is completed, write ${path.join(input.root, "code-verification.json")} using the exact contract below. Mark passed only when all accepted requirements and current-gate acceptance criteria are implemented or explicitly evidenced; list every remaining issue in unresolved. Every evidence_refs item must already exist as a relative workspace path or run://${input.run.id}/ path. Do not claim a browser or other check receipt that was not retained. Then finish: ${finishCommand(context, input.run.id, input.projectRoot, path.join(input.root, "code-verification.json"))}`,
|
|
446
|
-
"
|
|
525
|
+
"A code_gate_failed response with continuation.kind=repair_required has already registered one repair Work with all failed receipts. End this Turn so the controller can dispatch it in a fresh child. Do not recreate the repair, copy origin IDs, write a repair task file or execute the child start command yourself. After repair, update semantic verification and finish again.",
|
|
447
526
|
"</execution_commands>",
|
|
448
527
|
"",
|
|
449
528
|
"<verification_contract>",
|
|
@@ -462,76 +541,6 @@ function coordinatorPrompt(context, input) {
|
|
|
462
541
|
export function profileCommands(workspaceRoot) {
|
|
463
542
|
return aggregateCheckDeclarations(workspaceRoot).map((check) => check.command);
|
|
464
543
|
}
|
|
465
|
-
function latestReceiptsByCommand(receipts) {
|
|
466
|
-
const latest = new Map();
|
|
467
|
-
for (const receipt of receipts)
|
|
468
|
-
for (const ref of receipt.check_refs)
|
|
469
|
-
latest.set(ref, receipt);
|
|
470
|
-
return [...new Set(latest.values())];
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* A CODE finish may be retried after a client loses the long command's output.
|
|
474
|
-
* Reusing complete, same-workspace receipts is correct; rerunning them is both
|
|
475
|
-
* expensive and can race the still-settling original invocation.
|
|
476
|
-
*/
|
|
477
|
-
export function reusableAggregateGateReceipts(context, input) {
|
|
478
|
-
const expected = new Set(input.checks.map((check) => check.id));
|
|
479
|
-
const current = workspaceFingerprint(input.workspaceRoot);
|
|
480
|
-
const latest = new Map();
|
|
481
|
-
for (const receipt of checkReceipts(context, { projectId: input.projectId, runId: input.runId })) {
|
|
482
|
-
if (receipt.scope === "aggregate")
|
|
483
|
-
for (const ref of receipt.check_refs)
|
|
484
|
-
if (expected.has(ref))
|
|
485
|
-
latest.set(ref, receipt);
|
|
486
|
-
}
|
|
487
|
-
if (latest.size !== expected.size)
|
|
488
|
-
return null;
|
|
489
|
-
const receipts = input.checks.map((check) => latest.get(check.id));
|
|
490
|
-
return receipts.every((receipt) => receipt.status === "passed" && receipt.workspace_fingerprint === current) ? receipts : null;
|
|
491
|
-
}
|
|
492
|
-
/** One aggregate gate per RUN; a second invocation must wait, never duplicate it. */
|
|
493
|
-
function acquireAggregateGateLock(root) {
|
|
494
|
-
const directory = path.join(root, "checks");
|
|
495
|
-
const lock = path.join(directory, ".aggregate-gate.lock");
|
|
496
|
-
fs.mkdirSync(directory, { recursive: true });
|
|
497
|
-
try {
|
|
498
|
-
const descriptor = fs.openSync(lock, "wx");
|
|
499
|
-
fs.writeFileSync(descriptor, JSON.stringify({ pid: process.pid, started_at: new Date().toISOString() }));
|
|
500
|
-
fs.closeSync(descriptor);
|
|
501
|
-
}
|
|
502
|
-
catch (error) {
|
|
503
|
-
if (!isExistingLock(error))
|
|
504
|
-
throw error;
|
|
505
|
-
const activePid = lockPid(lock);
|
|
506
|
-
if (activePid && processAlive(activePid)) {
|
|
507
|
-
throw new AppError("code_gate_in_progress", "CODE aggregate gate is already running; wait for that exact stage finish command to return", 1, { pid: activePid, lock });
|
|
508
|
-
}
|
|
509
|
-
fs.rmSync(lock, { force: true });
|
|
510
|
-
return acquireAggregateGateLock(root);
|
|
511
|
-
}
|
|
512
|
-
return () => fs.rmSync(lock, { force: true });
|
|
513
|
-
}
|
|
514
|
-
function isExistingLock(error) {
|
|
515
|
-
return Boolean(error && typeof error === "object" && error.code === "EEXIST");
|
|
516
|
-
}
|
|
517
|
-
function lockPid(lock) {
|
|
518
|
-
try {
|
|
519
|
-
const parsed = JSON.parse(fs.readFileSync(lock, "utf8"));
|
|
520
|
-
return Number.isInteger(parsed.pid) && Number(parsed.pid) > 0 ? Number(parsed.pid) : null;
|
|
521
|
-
}
|
|
522
|
-
catch {
|
|
523
|
-
return null;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
function processAlive(pid) {
|
|
527
|
-
try {
|
|
528
|
-
process.kill(pid, 0);
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
catch {
|
|
532
|
-
return false;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
544
|
/**
|
|
536
545
|
* The immutable Work result is a repair history. The stage report is a current
|
|
537
546
|
* acceptance projection, so a registered receipt from an older check epoch
|
|
@@ -545,8 +554,8 @@ export function verificationProjection(works, receipts, options = {}) {
|
|
|
545
554
|
for (const work of works) {
|
|
546
555
|
const result = readJsonResult(work.result);
|
|
547
556
|
for (const item of result.evidence ?? [])
|
|
548
|
-
if (item.
|
|
549
|
-
rawEvidence.set(item.
|
|
557
|
+
if (item.obligation_ref)
|
|
558
|
+
rawEvidence.set(item.obligation_ref, unique([...(rawEvidence.get(item.obligation_ref) ?? []), ...(item.refs ?? [])]));
|
|
550
559
|
}
|
|
551
560
|
const acceptance = uniqueBy(packets.flatMap((value) => value.acceptance), (value) => value.criterion_id);
|
|
552
561
|
const historicalReceiptRefs = new Set((options.historicalReceipts ?? receipts).flatMap((receipt) => receiptReferences(receipt, options)));
|
|
@@ -561,18 +570,16 @@ export function verificationProjection(works, receipts, options = {}) {
|
|
|
561
570
|
return [item.criterion_id, { evidenceRefs, historical }];
|
|
562
571
|
}));
|
|
563
572
|
const hasEvidence = (criterionId) => (acceptanceEvidence.get(criterionId)?.evidenceRefs.length ?? 0) > 0;
|
|
564
|
-
const isPassed = (checkId
|
|
573
|
+
const isPassed = (checkId) => receiptByDeclaration.get(checkId)?.status === "passed";
|
|
565
574
|
return {
|
|
566
575
|
checks: declarations.map((check) => {
|
|
567
576
|
const receipt = receiptByDeclaration.get(check.id);
|
|
568
|
-
|
|
569
|
-
return { id: check.id, run_at: check.run_at, status: receipt?.status ?? (externallyEvidenced ? "evidence_recorded" : ["work", "code", "readiness"].includes(check.run_at) ? "failed" : "not_due"), receipt_ref: receipt?.receipt_path ?? null };
|
|
577
|
+
return { id: check.id, run_at: check.run_at, status: receipt?.status ?? (["work", "code", "readiness"].includes(check.run_at) ? "failed" : "not_due"), receipt_ref: receipt?.receipt_path ?? null };
|
|
570
578
|
}),
|
|
571
579
|
acceptance: acceptance.map((item) => {
|
|
572
|
-
const
|
|
573
|
-
const confirmed = item.check_refs.every((id) => isPassed(id, item.criterion_id)) && hasEvidence(item.criterion_id);
|
|
580
|
+
const confirmed = item.check_refs.every((id) => isPassed(id)) && hasEvidence(item.criterion_id);
|
|
574
581
|
const evidence = acceptanceEvidence.get(item.criterion_id) ?? { evidenceRefs: [], historical: [] };
|
|
575
|
-
return { criterion_id: item.criterion_id, gate: item.gate, status: confirmed ?
|
|
582
|
+
return { criterion_id: item.criterion_id, gate: item.gate, status: confirmed ? "confirmed" : ["work", "code", "readiness"].includes(item.gate) ? "unresolved" : "not_due", check_refs: item.check_refs, evidence_refs: evidence.evidenceRefs, ...(evidence.historical.length ? { reported_evidence_refs: evidence.historical } : {}) };
|
|
576
583
|
})
|
|
577
584
|
};
|
|
578
585
|
}
|
|
@@ -642,69 +649,71 @@ function requireRun(context, projectId, runId) {
|
|
|
642
649
|
return run;
|
|
643
650
|
}
|
|
644
651
|
/** CODE, not PROTOCOLIZE, makes a checkout ready for project tooling. */
|
|
645
|
-
|
|
652
|
+
export function isReusablePreparedCodeReadiness(value, expected) {
|
|
653
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
654
|
+
return false;
|
|
655
|
+
const receipt = value;
|
|
656
|
+
return receipt.schema_id === "dd-flow/workspace-readiness@2"
|
|
657
|
+
&& receipt.status === "passed"
|
|
658
|
+
&& receipt.prepared_by === expected.ownerId
|
|
659
|
+
&& receipt.workspace_root === expected.workspaceRoot
|
|
660
|
+
&& receipt.command === expected.command
|
|
661
|
+
&& receipt.policy_ref === expected.policyRef;
|
|
662
|
+
}
|
|
663
|
+
function readPreparedReadiness(file) {
|
|
664
|
+
try {
|
|
665
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
666
|
+
}
|
|
667
|
+
catch {
|
|
668
|
+
return null;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
async function ensureCodeWorkspaceReady(context, projectId, run, root, readinessChecks, progress, options = {}) {
|
|
646
672
|
const profile = JSON.parse(run.index_json);
|
|
647
673
|
const bootstrap = profile.execution_profile?.settings?.code_bootstrap;
|
|
648
674
|
if (!bootstrap || typeof bootstrap.command !== "string" || typeof bootstrap.policy_ref !== "string") {
|
|
649
675
|
throw new AppError("execution_profile_invalid", "CODE requires a frozen bootstrap command in the RUN execution profile", 1, { run_id: run.id });
|
|
650
676
|
}
|
|
651
677
|
const receiptPath = path.join(root, "workspace-readiness.json");
|
|
652
|
-
const
|
|
653
|
-
if (
|
|
654
|
-
|
|
678
|
+
const ownerId = context.env.DD_FLOW_CODE_READINESS_OWNER;
|
|
679
|
+
if (!options.force && ownerId && isReusablePreparedCodeReadiness(readPreparedReadiness(receiptPath), {
|
|
680
|
+
ownerId,
|
|
681
|
+
workspaceRoot: run.workspace_root,
|
|
682
|
+
command: bootstrap.command,
|
|
683
|
+
policyRef: bootstrap.policy_ref
|
|
684
|
+
})) {
|
|
685
|
+
progress?.("workspace bootstrap already prepared by this controller");
|
|
686
|
+
return readPreparedReadiness(receiptPath);
|
|
687
|
+
}
|
|
688
|
+
// Unowned and stale receipts are never trusted: a direct stage entry still
|
|
689
|
+
// performs its own readiness check. Only the same controller can hand a
|
|
690
|
+
// freshly prepared workspace to its immediately following lifecycle call.
|
|
655
691
|
const startedAt = new Date().toISOString();
|
|
656
692
|
progress?.(`workspace bootstrap started: ${bootstrap.command}`);
|
|
657
|
-
const result = await
|
|
693
|
+
const result = await runWorkspaceBootstrap(context, { projectId, runId: run.id, runHome: requireHome(run), workspaceRoot: run.workspace_root, command: bootstrap.command, artifactDir: "05-code/readiness", progress });
|
|
658
694
|
const bootstrapPassed = result.exit_code === 0 && !result.error;
|
|
659
695
|
const checks = bootstrapPassed && readinessChecks.length
|
|
660
696
|
? await runCodeChecks(context, { projectId, runId: run.id, runHome: requireHome(run), workspaceRoot: run.workspace_root, scope: "aggregate", artifactDir: "05-code/readiness", checks: readinessChecks, ...(progress ? { progress } : {}) })
|
|
661
697
|
: [];
|
|
662
|
-
const receipt = { schema_id: "dd-flow/workspace-readiness@2", workspace_root: run.workspace_root, command: bootstrap.command, policy_ref: bootstrap.policy_ref, started_at: startedAt, finished_at: new Date().toISOString(), status: bootstrapPassed && checks.every((check) => check.status === "passed") ? "passed" : "failed", exit_code: result.exit_code, stdout: result.stdout, stderr: result.stderr, check_declarations: readinessChecks, checks: checks.map((check) => ({ id: check.id, status: check.status, receipt_path: check.receipt_path })), ...(result.error ? { error: result.error } : {}), receipt_path: receiptPath };
|
|
698
|
+
const receipt = { schema_id: "dd-flow/workspace-readiness@2", workspace_root: run.workspace_root, command: bootstrap.command, policy_ref: bootstrap.policy_ref, started_at: startedAt, finished_at: new Date().toISOString(), status: bootstrapPassed && checks.every((check) => check.status === "passed") ? "passed" : "failed", ...(ownerId ? { prepared_by: ownerId } : {}), process_id: result.process_id, stdout_path: result.stdout_path, stderr_path: result.stderr_path, completion_path: result.completion_path, exit_code: result.exit_code, stdout: result.stdout, stderr: result.stderr, check_declarations: readinessChecks, checks: checks.map((check) => ({ id: check.id, status: check.status, receipt_path: check.receipt_path })), ...(result.error ? { error: result.error } : {}), receipt_path: receiptPath };
|
|
663
699
|
fs.writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
664
700
|
if (receipt.status !== "passed")
|
|
665
701
|
throw new AppError("workspace_readiness_failed", "CODE workspace bootstrap failed", 1, receipt);
|
|
666
702
|
progress?.("workspace bootstrap passed");
|
|
667
703
|
return receipt;
|
|
668
704
|
}
|
|
669
|
-
function runBootstrap(command, cwd, environment, progress) {
|
|
670
|
-
return new Promise((resolve) => {
|
|
671
|
-
const started = Date.now();
|
|
672
|
-
const child = spawn("/bin/sh", ["-lc", command], { cwd, env: environment, stdio: ["ignore", "pipe", "pipe"] });
|
|
673
|
-
let stdout = "";
|
|
674
|
-
let stderr = "";
|
|
675
|
-
let error = null;
|
|
676
|
-
child.stdout.setEncoding("utf8").on("data", (chunk) => { stdout += chunk; });
|
|
677
|
-
child.stderr.setEncoding("utf8").on("data", (chunk) => { stderr += chunk; });
|
|
678
|
-
const heartbeat = setInterval(() => progress?.(`workspace bootstrap still running (${Math.floor((Date.now() - started) / 1000)}s)`), 15_000);
|
|
679
|
-
const timeout = setTimeout(() => child.kill("SIGTERM"), 15 * 60 * 1000);
|
|
680
|
-
child.once("error", (value) => { error = value.message; });
|
|
681
|
-
child.once("close", (exitCode, signal) => {
|
|
682
|
-
clearInterval(heartbeat);
|
|
683
|
-
clearTimeout(timeout);
|
|
684
|
-
resolve({ exit_code: exitCode, stdout, stderr, error: error ?? (signal ? `terminated by ${signal}` : null) });
|
|
685
|
-
});
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
function readReadiness(file) {
|
|
689
|
-
try {
|
|
690
|
-
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
691
|
-
}
|
|
692
|
-
catch {
|
|
693
|
-
return null;
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
705
|
function requireHome(run) {
|
|
697
706
|
if (!run.run_root)
|
|
698
707
|
throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1);
|
|
699
708
|
return run.run_root;
|
|
700
709
|
}
|
|
701
|
-
function finishCommand(context, runId, projectRoot, verificationFile) {
|
|
702
|
-
return
|
|
710
|
+
export function finishCommand(context, runId, projectRoot, verificationFile, retryCheckId) {
|
|
711
|
+
return managedLifecycleCommand(context, runtimeCommand(context, ["stage", "finish", runId, "--stage", "code", "--verification-file", verificationFile, "--project-root", projectRoot, "--json", "--progress-jsonl", ...checkRetryArgs(context, projectRoot, runId, retryCheckId)]));
|
|
703
712
|
}
|
|
704
713
|
function readVerification(context, input) {
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
714
|
+
return prepareJsonSchema(input.file, "CODE verification", {
|
|
715
|
+
schemaName: "code-verification", projectRoot: input.projectRoot, ddFlowHome: context.ddFlowHome, runId: input.runId, runRoot: input.runRoot
|
|
716
|
+
}).value;
|
|
708
717
|
}
|
|
709
718
|
function verificationForFinish(context, input) {
|
|
710
719
|
return readVerification(context, input);
|
|
@@ -726,6 +735,8 @@ export function writeProtocolFlowStatus(workspaceRoot, runHome, runId, status) {
|
|
|
726
735
|
}
|
|
727
736
|
function nextAction(run, changedPaths) {
|
|
728
737
|
const index = JSON.parse(run.index_json);
|
|
738
|
+
if (index.variables?.["merge.source_repair"])
|
|
739
|
+
return "start_code_review";
|
|
729
740
|
const requested = index.settings?.code_review?.mode ?? "auto";
|
|
730
741
|
const enabled = requested === "off" ? false : requested === "auto" ? changedPaths.length > 0 : true;
|
|
731
742
|
const stopTarget = index.execution_profile?.settings?.stop_target;
|
|
@@ -741,19 +752,19 @@ function stageStatus(run, name) {
|
|
|
741
752
|
return null;
|
|
742
753
|
}
|
|
743
754
|
}
|
|
744
|
-
function
|
|
755
|
+
function isMergeSourceRepairAttempt(run) {
|
|
745
756
|
try {
|
|
746
|
-
const value = JSON.parse(
|
|
747
|
-
return
|
|
757
|
+
const value = JSON.parse(run.index_json);
|
|
758
|
+
return Boolean(value.variables?.["merge.source_repair"]);
|
|
748
759
|
}
|
|
749
760
|
catch {
|
|
750
|
-
return
|
|
761
|
+
return false;
|
|
751
762
|
}
|
|
752
763
|
}
|
|
753
|
-
function
|
|
764
|
+
function changedPathsFromReport(file) {
|
|
754
765
|
try {
|
|
755
|
-
const value = JSON.parse(
|
|
756
|
-
return Array.isArray(value.
|
|
766
|
+
const value = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
767
|
+
return Array.isArray(value.semantic?.changed_files) ? value.semantic.changed_files.filter((item) => typeof item === "string") : [];
|
|
757
768
|
}
|
|
758
769
|
catch {
|
|
759
770
|
return [];
|
|
@@ -772,6 +783,28 @@ function uniqueBy(values, key) {
|
|
|
772
783
|
return true;
|
|
773
784
|
});
|
|
774
785
|
}
|
|
786
|
+
/** Repair pre-atomic terminal stages without repeating accepted checks or rewinding a successor. */
|
|
787
|
+
export function resumeAcceptedCodeStage(context, run, projectRoot, stage, report, next) {
|
|
788
|
+
const index = JSON.parse(run.index_json);
|
|
789
|
+
if ((index.current_stage ?? index.stage_runs.at(-1)?.stage) !== stage)
|
|
790
|
+
return;
|
|
791
|
+
const terminal = next === "code_completed" || next === "code_review_completed";
|
|
792
|
+
if (!terminal && index.next_action === next)
|
|
793
|
+
return;
|
|
794
|
+
withStageSettlement(context, () => {
|
|
795
|
+
if (!terminal) {
|
|
796
|
+
advanceFlowRun(context, { settlementStage: stage, projectRoot, runId: run.id, status: "running", verdict: `${stage.replaceAll("-", "_")}_completed`, nextAction: next });
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
const work = context.db.get("SELECT * FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id IS NULL ORDER BY created_at LIMIT 1", [run.project_id, run.id]);
|
|
800
|
+
if (!work)
|
|
801
|
+
throw new AppError("runtime_missing", "Accepted stage has no root coordinator", 1);
|
|
802
|
+
if (work.status !== "completed")
|
|
803
|
+
finishStageCoordinatorWork(context, work.work_id, JSON.stringify(report, null, 2));
|
|
804
|
+
if (index.status !== "done")
|
|
805
|
+
completeFlowRun(context, { projectRoot, runId: run.id, status: "done", verdict: next });
|
|
806
|
+
});
|
|
807
|
+
}
|
|
775
808
|
function writeReport(root, report) {
|
|
776
809
|
writeStageReport(root, report);
|
|
777
810
|
}
|