@deksden-com/dd-flow-cli 0.9.0-beta.52 → 0.9.0-beta.53
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 +6 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/help.js +13 -3
- package/dist/cli/run-cli.js +74 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +3 -4
- package/dist/harness-runtime/bin/dd-zcode.mjs +13 -1
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +4 -19
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +20 -4
- package/dist/harness-runtime/lib/dd-zcode.mjs +85 -9
- package/dist/harness-runtime/lib/native-hook-command.mjs +33 -0
- package/dist/services/cli-operation-classifier.js +2 -0
- package/dist/services/eval-snapshots.js +1 -1
- package/dist/services/external-work-launch.js +5 -2
- package/dist/services/hooks.js +158 -113
- package/dist/services/lifecycle-command.js +63 -5
- package/dist/services/lifecycle-invocations.js +306 -0
- package/dist/services/merge-server.js +6 -4
- package/dist/services/recovery-snapshot-database.js +5 -1
- package/dist/services/run-controller-recovery.js +4 -1
- package/dist/services/run-controller.js +11 -3
- package/dist/services/run-recovery.js +11 -1
- package/dist/services/stage-pause.js +9 -5
- package/dist/services/vnext-code-review.js +5 -4
- package/dist/services/vnext-code.js +3 -2
- package/dist/services/vnext-merge.js +5 -4
- package/dist/services/vnext-plan-review.js +8 -7
- package/dist/services/vnext-plan.js +3 -2
- package/dist/services/vnext-protocolize.js +5 -4
- package/dist/services/vnext-specify.js +5 -4
- package/dist/services/work-registry.js +6 -3
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { runWorkspaceBootstrap } from "./workspace-bootstrap.js";
|
|
3
4
|
import fs from "node:fs";
|
|
@@ -372,7 +373,7 @@ export async function repairVnextMerge(context, input) {
|
|
|
372
373
|
if (request.project_id === project.id && request.status === "superseded" && errorCode(request) === "merge_source_repair") {
|
|
373
374
|
const saved = JSON.parse(request.last_error_json);
|
|
374
375
|
releaseMergeLane(context, projectRoot, request, `MERGE ${request.merge_request_id} moved to source repair`);
|
|
375
|
-
return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: { repair_work_id: saved.repair_work_id }, next: { kind: "start_stage", stage: "code", command: `${flowCommand(context)} stage start ${request.run_id} --stage code --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl` } };
|
|
376
|
+
return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: { repair_work_id: saved.repair_work_id }, next: { kind: "start_stage", stage: "code", command: managedLifecycleCommand(context, `${flowCommand(context)} stage start ${request.run_id} --stage code --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl`) } };
|
|
376
377
|
}
|
|
377
378
|
if (request.project_id !== project.id || request.status !== "action_required" || errorCode(request) !== "merge_gate_failed") {
|
|
378
379
|
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) });
|
|
@@ -396,9 +397,9 @@ export async function repairVnextMerge(context, input) {
|
|
|
396
397
|
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]);
|
|
397
398
|
releaseMergeLane(context, projectRoot, request, `MERGE ${request.merge_request_id} moved to source repair`);
|
|
398
399
|
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 });
|
|
399
|
-
return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: repair, next: { kind: "start_stage", stage: "code", command: `${flowCommand(context)} stage start ${request.run_id} --stage code --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl` } };
|
|
400
|
+
return { ok: true, run_id: request.run_id, merge_request_id: request.merge_request_id, status: "superseded", source_repair: repair, next: { kind: "start_stage", stage: "code", command: managedLifecycleCommand(context, `${flowCommand(context)} stage start ${request.run_id} --stage code --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl`) } };
|
|
400
401
|
}
|
|
401
|
-
function mergePrompt(context, input) { const pause = `${flowCommand(context)} stage pause ${input.run.id} --stage merge --work ${input.request.executor_work_id} --project-root ${JSON.stringify(input.projectRoot)} --question-stdin --json
|
|
402
|
+
function mergePrompt(context, input) { const pause = managedLifecycleCommand(context, `${flowCommand(context)} stage pause ${input.run.id} --stage merge --work ${input.request.executor_work_id} --project-root ${JSON.stringify(input.projectRoot)} --question-stdin --json`); const repair = `${flowCommand(context)} merge repair ${input.request.merge_request_id} --project-root ${JSON.stringify(input.projectRoot)} --json`; 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(executionSettings(input.run).merge_delivery)}`, `- cleanup: ${JSON.stringify(executionSettings(input.run).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>", ...effectiveMergeChecks(input.run, input.request).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 use the returned finish command with --retry-check <receipt-id> --reason "<what was restored>". 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 with the exact heredoc below, ask the returned user_message, then use the exact resume command returned by CLI:", "```sh", stagePauseCommandTemplate(pause), "```", "</execution_contract>", ""].join("\n"); }
|
|
402
403
|
function mergeReport(context, run, request, semantic, receipts) { const now = context.now(); const cleanup = cleanupReceiptPath(run); return { schema_id: "dd-flow/stage-report@2", run_id: run.id, stage, generated_at: now, verdict: "done", summary: semantic.summary, semantic: { result: semantic.summary, acceptance: ["source_commit_frozen", "integration_commit_created", "merge_gate_passed", "delivery_confirmed"], changed_files: [], checks: receipts.map((item) => item.command), evidence: [applyReceiptPath(context, request), ...receipts.map((item) => item.receipt_path), ...(fs.existsSync(cleanup) ? [cleanup] : [])], next_action: "merge_completed", merge: { merge_request_id: request.merge_request_id, work_id: request.executor_work_id, protocols: JSON.parse(request.protocol_ids_json), source_commit: request.source_commit, execution_target_head: request.execution_target_head, accepted_tree: request.accepted_tree, integration_commit: request.integration_commit, route: request.execution_route, delivery: executionSettings(run).merge_delivery, cleanup: executionSettings(run).merge_cleanup, verification_summary: semantic.verification_summary, residual_risks: semantic.residual_risks } }, mechanical: { started_at: request.lock_acquired_at, finished_at: now, git: gitFacts(request.target_workspace), queue: queueStatus(context, request) }, artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html" }, validation: { status: "passed" } }; }
|
|
403
404
|
function effectiveMergeChecks(run, request) { return readFrozenMergeGate(path.join(requireHome(run), stageDir, "merge-gate.json"), request.merge_request_id).checks; }
|
|
404
405
|
function planChecks(workspace, protocols) { return protocols.flatMap((protocol) => { const file = path.join(workspace, ".memory-bank", "protocol", protocol, "plan.json"); if (!fs.existsSync(file))
|
|
@@ -560,4 +561,4 @@ function findRootWork(context, projectId, runId) { const work = context.db.get("
|
|
|
560
561
|
function requireRootWork(context, projectId, runId) { const work = findRootWork(context, projectId, runId); if (work.status !== "running")
|
|
561
562
|
throw new AppError("runtime_missing", "vNext RUN has no running root Work", 1); return work; }
|
|
562
563
|
function applyCommand(context, request, projectRoot) { return `${flowCommand(context)} merge apply ${request.merge_request_id} --work ${request.executor_work_id} --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl`; }
|
|
563
|
-
function finishCommand(context, runId, request, projectRoot) { return `${flowCommand(context)} stage finish ${runId} --stage merge --request ${request.merge_request_id} --work ${request.executor_work_id} --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl
|
|
564
|
+
function finishCommand(context, runId, request, projectRoot) { return managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${runId} --stage merge --request ${request.merge_request_id} --work ${request.executor_work_id} --project-root ${JSON.stringify(projectRoot)} --json --progress-jsonl`); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { withStageSettlement } from "./runs.js";
|
|
3
4
|
import fs from "node:fs";
|
|
@@ -73,7 +74,7 @@ export function startVnextPlanReview(context, input) {
|
|
|
73
74
|
attachFlowRunStage(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
|
|
74
75
|
return withStageSettlement(context, () => {
|
|
75
76
|
const registered = registerCode(context, project.id, run.id, batch);
|
|
76
|
-
const report = reportFor({ run, mode: effective, requested, outcome: "off", groups: [], batchChecksum, planChecksum, code: registered, now, projectRoot, flow: flowCommand(context) });
|
|
77
|
+
const report = reportFor(context, { run, mode: effective, requested, outcome: "off", groups: [], batchChecksum, planChecksum, code: registered, now, projectRoot, flow: flowCommand(context) });
|
|
77
78
|
writeReport(root, report);
|
|
78
79
|
completeFlowRunStage(context, { projectRoot, runId: run.id, stage, status: "done", data: "stage-report.json", dataSchemaId: "dd-flow/stage-report@1", report: "stage-report.md", stageReport: "stage-report.html" });
|
|
79
80
|
advanceFlowRun(context, { settlementStage: "plan-review", projectRoot, runId: run.id, status: "running", verdict: "plan_review_off", nextAction: "start_code" });
|
|
@@ -248,7 +249,7 @@ export function finishVnextPlanReview(context, input) {
|
|
|
248
249
|
const registered = registerCode(context, project.id, run.id, batch);
|
|
249
250
|
const now = context.now();
|
|
250
251
|
settleReviewParent(context, { projectRoot, projectId: project.id, runId: run.id, parentWorkId: parent.work_id, status: "completed", result: decision.summary, reason: "plan_review_completed", resultPath: path.join(root, "stage-report.json") });
|
|
251
|
-
const report = reportFor({ run, mode: contextFile.system?.effective_mode ?? "standard", requested: contextFile.system?.requested_mode ?? "auto", outcome: "accepted", groups, batchChecksum: contextFile.system?.batch_checksum ?? finalBatchChecksum, planChecksum: contextFile.system?.plan_checksum ?? finalPlanChecksum, finalBatchChecksum, finalPlanChecksum, initialPlanRevision: reviewedPlanRevision, finalPlanRevision: planRevision, code: registered, now, projectRoot, decision, children: latestChildren, flow: flowCommand(context) });
|
|
252
|
+
const report = reportFor(context, { run, mode: contextFile.system?.effective_mode ?? "standard", requested: contextFile.system?.requested_mode ?? "auto", outcome: "accepted", groups, batchChecksum: contextFile.system?.batch_checksum ?? finalBatchChecksum, planChecksum: contextFile.system?.plan_checksum ?? finalPlanChecksum, finalBatchChecksum, finalPlanChecksum, initialPlanRevision: reviewedPlanRevision, finalPlanRevision: planRevision, code: registered, now, projectRoot, decision, children: latestChildren, flow: flowCommand(context) });
|
|
252
253
|
applyReviewResults(home, latestChildren, decision);
|
|
253
254
|
writeReport(root, report);
|
|
254
255
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
@@ -470,7 +471,7 @@ function finishTerminalReview(context, input) {
|
|
|
470
471
|
context.db.run("UPDATE works SET status = ?, result = ?, completed_at = ?, updated_at = ? WHERE work_id = ? AND status = 'running'", [childStatus, input.decision.summary, now, now, rootWork]);
|
|
471
472
|
closeRunningWorkSession(context, rootWork, childStatus, now);
|
|
472
473
|
const settledChildren = context.db.all("SELECT work_id, task, status, result, created_at FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id = ?", [input.projectId, input.run.id, input.parentWorkId]);
|
|
473
|
-
const report = reportFor({ run: input.run, mode: input.contextFile.system?.effective_mode ?? "standard", requested: input.contextFile.system?.requested_mode ?? "auto", outcome: input.decision.outcome, groups: input.contextFile.groups ?? [], batchChecksum: input.contextFile.system?.batch_checksum ?? checksum(input.batch), planChecksum: input.contextFile.system?.plan_checksum ?? planSetChecksum(requireHome(input.run), input.run.workspace_root), code: {}, now, projectRoot: input.projectRoot, decision: input.decision, children: latestReviewChildren(settledChildren), flow: flowCommand(context) });
|
|
474
|
+
const report = reportFor(context, { run: input.run, mode: input.contextFile.system?.effective_mode ?? "standard", requested: input.contextFile.system?.requested_mode ?? "auto", outcome: input.decision.outcome, groups: input.contextFile.groups ?? [], batchChecksum: input.contextFile.system?.batch_checksum ?? checksum(input.batch), planChecksum: input.contextFile.system?.plan_checksum ?? planSetChecksum(requireHome(input.run), input.run.workspace_root), code: {}, now, projectRoot: input.projectRoot, decision: input.decision, children: latestReviewChildren(settledChildren), flow: flowCommand(context) });
|
|
474
475
|
writeReport(input.root, report);
|
|
475
476
|
refreshRunWorkProjection(context, input.projectId, input.run.id);
|
|
476
477
|
const stageStatus = input.decision.outcome === "cancelled" ? "skipped" : input.decision.outcome === "failed" ? "failed" : "blocked";
|
|
@@ -489,7 +490,7 @@ function settleReviewChildren(context, input) {
|
|
|
489
490
|
function closeRunningWorkSession(context, workId, status, now) {
|
|
490
491
|
context.db.run("UPDATE work_sessions SET status = ?, completed_at = ?, updated_at = ? WHERE work_id = ? AND status = 'running'", [status, now, now, workId]);
|
|
491
492
|
}
|
|
492
|
-
function reportFor(input) {
|
|
493
|
+
function reportFor(context, input) {
|
|
493
494
|
const accepted = input.outcome === "accepted" || input.outcome === "off";
|
|
494
495
|
const result = input.outcome === "off" ? "Independent PLAN review was explicitly or automatically skipped." : accepted ? "Independent PLAN review accepted the plan." : `PLAN-REVIEW stopped with outcome: ${input.outcome}.`;
|
|
495
496
|
const decision = input.decision;
|
|
@@ -498,15 +499,15 @@ function reportFor(input) {
|
|
|
498
499
|
const sessionCommand = `${input.flow} stat run sessions ls --run ${input.run.id} --project-root ${JSON.stringify(input.projectRoot)} --json`;
|
|
499
500
|
const usageCommand = `${input.flow} stat usage --run ${input.run.id} --project-root ${JSON.stringify(input.projectRoot)} --json`;
|
|
500
501
|
const changedFiles = correction?.status === "applied" ? correction.changed_paths : [];
|
|
501
|
-
return { schema_id: "dd-flow/stage-report@1", run_id: input.run.id, stage, generated_at: input.now, verdict: accepted ? "done" : input.outcome, semantic: { result, acceptance: [input.outcome], changed_files: changedFiles, checks: ["accepted PLAN report", "generated CODE batch", "reviewer evidence", ...(correction ? ["mechanical correction receipt"] : [])], evidence: [], next_action: accepted ? "start_code" : "resolve_plan_review_blocker", plan_review: { requested_mode: input.requested, effective_mode: input.mode, outcome: input.outcome, groups: input.groups.map((group) => group.key), initial_plan_revision: input.initialPlanRevision ?? null, final_plan_revision: input.finalPlanRevision ?? input.initialPlanRevision ?? null, initial_plan_checksum: input.planChecksum, final_plan_checksum: input.finalPlanChecksum ?? input.planChecksum, initial_batch_checksum: input.batchChecksum, final_batch_checksum: input.finalBatchChecksum ?? input.batchChecksum, correction, decision: input.decision ?? null, reviewer_works: input.children?.map((child) => ({ work_id: child.work_id, status: child.status, verdict: reviewerVerdict(child.result) })) ?? [] }, plan_checksum: input.finalPlanChecksum ?? input.planChecksum, batch_checksum: input.finalBatchChecksum ?? input.batchChecksum, code: input.code }, mechanical: { started_at: timing.started_at, finished_at: input.now, wall_clock_ms: timing.wall_clock_ms, git: gitFacts(input.run.workspace_root), session_stats_command: sessionCommand, usage_stats_command: usageCommand, ...(accepted ? { next_command: `${input.flow} stage start ${input.run.id} --stage code --project-root ${JSON.stringify(input.projectRoot)} --json` } : {}) }, artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html", summary: "stage-report.md" }, validation: { permission_scope: "known_targets_only", memory_bank_scope: "changed_files_and_links_only", status: "passed" } };
|
|
502
|
+
return { schema_id: "dd-flow/stage-report@1", run_id: input.run.id, stage, generated_at: input.now, verdict: accepted ? "done" : input.outcome, semantic: { result, acceptance: [input.outcome], changed_files: changedFiles, checks: ["accepted PLAN report", "generated CODE batch", "reviewer evidence", ...(correction ? ["mechanical correction receipt"] : [])], evidence: [], next_action: accepted ? "start_code" : "resolve_plan_review_blocker", plan_review: { requested_mode: input.requested, effective_mode: input.mode, outcome: input.outcome, groups: input.groups.map((group) => group.key), initial_plan_revision: input.initialPlanRevision ?? null, final_plan_revision: input.finalPlanRevision ?? input.initialPlanRevision ?? null, initial_plan_checksum: input.planChecksum, final_plan_checksum: input.finalPlanChecksum ?? input.planChecksum, initial_batch_checksum: input.batchChecksum, final_batch_checksum: input.finalBatchChecksum ?? input.batchChecksum, correction, decision: input.decision ?? null, reviewer_works: input.children?.map((child) => ({ work_id: child.work_id, status: child.status, verdict: reviewerVerdict(child.result) })) ?? [] }, plan_checksum: input.finalPlanChecksum ?? input.planChecksum, batch_checksum: input.finalBatchChecksum ?? input.batchChecksum, code: input.code }, mechanical: { started_at: timing.started_at, finished_at: input.now, wall_clock_ms: timing.wall_clock_ms, git: gitFacts(input.run.workspace_root), session_stats_command: sessionCommand, usage_stats_command: usageCommand, ...(accepted ? { next_command: managedLifecycleCommand(context, `${input.flow} stage start ${input.run.id} --stage code --project-root ${JSON.stringify(input.projectRoot)} --json`) } : {}) }, artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html", summary: "stage-report.md" }, validation: { permission_scope: "known_targets_only", memory_bank_scope: "changed_files_and_links_only", status: "passed" } };
|
|
502
503
|
}
|
|
503
504
|
function stageTiming(run, now) { const started = readIndex(run).stage_runs?.find((item) => item.stage === stage && typeof item.started_at === "string"); const startedAt = started?.started_at ?? now; const milliseconds = Date.parse(now) - Date.parse(startedAt); return { started_at: startedAt, wall_clock_ms: Number.isFinite(milliseconds) && milliseconds >= 0 ? milliseconds : null }; }
|
|
504
505
|
function writeReport(root, report) {
|
|
505
506
|
writeStageReport(root, report);
|
|
506
507
|
}
|
|
507
|
-
function codeCommand(context, runId, projectRoot) { return `${flowCommand(context)} stage start ${runId} --stage code --project-root ${JSON.stringify(projectRoot)} --json
|
|
508
|
+
function codeCommand(context, runId, projectRoot) { return managedLifecycleCommand(context, `${flowCommand(context)} stage start ${runId} --stage code --project-root ${JSON.stringify(projectRoot)} --json`); }
|
|
508
509
|
function dispatchCommand(context, runId, projectRoot) { return `${flowCommand(context)} plan-review dispatch ${runId} --project-root ${JSON.stringify(projectRoot)} --json`; }
|
|
509
|
-
function finishCommand(context, runId, projectRoot, decision) { return `${flowCommand(context)} stage finish ${runId} --stage plan-review --project-root ${JSON.stringify(projectRoot)} --decision-file ${JSON.stringify(decision)} --json
|
|
510
|
+
function finishCommand(context, runId, projectRoot, decision) { return managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${runId} --stage plan-review --project-root ${JSON.stringify(projectRoot)} --decision-file ${JSON.stringify(decision)} --json`); }
|
|
510
511
|
function capacityRecordCommand(context, runId, projectRoot, availableSlots) { return `${flowCommand(context)} run capacity record ${runId} --available-slots ${availableSlots} --project-root ${JSON.stringify(projectRoot)} --json`; }
|
|
511
512
|
function requireRun(context, projectRoot, runId) { const project = requireProjectByRoot(context, projectRoot); const run = context.db.get("SELECT id, project_id, workspace_root, run_root, index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]); if (!run)
|
|
512
513
|
throw new AppError("not_found", "RUN is not registered", 1); return run; }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import { withStageSettlement } from "./runs.js";
|
|
3
4
|
import fs from "node:fs";
|
|
@@ -71,7 +72,7 @@ export function startVnextPlan(context, input) {
|
|
|
71
72
|
const identities = protocols.map((protocolId) => planIdentity(home, run.id, protocolId, owned.get(protocolId) ?? []));
|
|
72
73
|
planPaths.forEach((file, index) => ensurePlanSkeleton(file, protocols[index], identities[index]));
|
|
73
74
|
mapPaths.forEach((file, index) => ensureAspectMapSkeleton(file, protocols[index], identities[index], run.workspace_root));
|
|
74
|
-
const finishCommand = `${flowCommand(context)} stage finish ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json
|
|
75
|
+
const finishCommand = managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json`);
|
|
75
76
|
const pauseCommand = stagePauseCommand(context, { runId: run.id, stage: "plan", workId: planWorkId, projectRoot });
|
|
76
77
|
const pauseCommandTemplate = stagePauseCommandTemplate(pauseCommand);
|
|
77
78
|
const validationCommands = protocols.flatMap((_, index) => [
|
|
@@ -137,7 +138,7 @@ function finishVnextPlanOwned(context, input) {
|
|
|
137
138
|
throw new AppError("trusted_session_binding_required", "PLAN finish requires its bound Agent WorkSession", 1, { work_id: work.work_id });
|
|
138
139
|
context.db.run("UPDATE work_sessions SET status = 'completed', result_path = ?, updated_at = ?, completed_at = ? WHERE id = ?", [path.join(root, "stage-report.json"), now, now, workSession.id]);
|
|
139
140
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
140
|
-
const reviewCommand = `${flowCommand(context)} stage start ${run.id} --stage plan-review --project-root ${JSON.stringify(projectRoot)} --json
|
|
141
|
+
const reviewCommand = managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage plan-review --project-root ${JSON.stringify(projectRoot)} --json`);
|
|
141
142
|
const report = { schema_id: "dd-flow/stage-report@1", run_id: run.id, stage: "plan", generated_at: now, verdict: "done", semantic: { result: `Accepted ${protocols.length} executable PLAN artifact${protocols.length === 1 ? "" : "s"}.`, acceptance: protocols, changed_files: [...planFiles.map((file) => path.relative(projectRoot, file)), ...mapFiles.map((file) => runRef(run.id, home, file)), runRef(run.id, home, batch)], checks: ["protocol-plan schema", "aspect-map schema", "cross-artifact references", "generated CODE batch"], evidence: [runRef(run.id, home, path.join(root, "stage-report.json"))], next_action: "start_plan_review", plans: planFiles.map((file) => path.relative(projectRoot, file)), aspect_maps: mapFiles.map((file) => runRef(run.id, home, file)), code_work_batch: runRef(run.id, home, batch), batch_checksum: batchChecksum }, mechanical: { started_at: stageStartedAt(home, now), finished_at: now, wall_clock_ms: Math.max(0, Date.parse(now) - Date.parse(stageStartedAt(home, now))), git: gitFacts(run.workspace_root), session_stats_command: `${flowCommand(context)} stat run sessions ls --run ${run.id} --project-root ${JSON.stringify(projectRoot)} --json`, usage_stats_command: `${flowCommand(context)} stat usage --run ${run.id} --project-root ${JSON.stringify(projectRoot)} --json`, next_command: reviewCommand }, artifacts: { json: "stage-report.json", markdown: "stage-report.md", html: "stage-report.html", summary: "stage-report.md" }, validation: { permission_scope: "known_targets_only", memory_bank_scope: "changed_files_and_links_only", status: "passed" } };
|
|
142
143
|
const reportJson = writeStageReport(root, report).json;
|
|
143
144
|
validateSchema({ schemaName: "stage-report", file: reportJson, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: home });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -61,13 +62,13 @@ export function prepareVnextProtocolize(context, input) {
|
|
|
61
62
|
if (!fs.existsSync(templatePath))
|
|
62
63
|
throw new AppError("not_found", "vNext PROTOCOLIZE prompt template is missing", 1, { path: templatePath });
|
|
63
64
|
const pauseCommand = stagePauseCommand(context, { runId: run.id, stage, workId: work.work_id, projectRoot });
|
|
64
|
-
const prompt = renderPrompt({ projectRoot, runId: run.id, workId: work.work_id, specifyPath, obligations, resultPath, handoff: input.handoff, catalog: catalog(projectRoot), policy, template: fs.readFileSync(templatePath, "utf8"), pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand),
|
|
65
|
+
const prompt = renderPrompt({ projectRoot, runId: run.id, workId: work.work_id, specifyPath, obligations, resultPath, handoff: input.handoff, catalog: catalog(projectRoot), policy, template: fs.readFileSync(templatePath, "utf8"), pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), finishCommand: managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --result-file ${JSON.stringify(resultPath)} --json`) });
|
|
65
66
|
fs.writeFileSync(promptPath, prompt);
|
|
66
67
|
// Preparation is deliberately non-productive: the standalone stage-start
|
|
67
68
|
// command atomically installs external context, binds the worker and opens
|
|
68
69
|
// the lifecycle Stage. Do not make this prompt an implicit running stage.
|
|
69
70
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "stage_prepared", work_id: work.work_id, stage, handoff_mode: input.handoff.stage_handoff.effective });
|
|
70
|
-
const directive = input.handoff.stage_handoff.effective === "same_session" ? { kind: "continue_current_session", stage, prompt_path: promptPath, worker_prompt_markdown: prompt } : { kind: "await_new_session", stage, work_id: work.work_id, prompt_path: promptPath, resume_command: `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json` };
|
|
71
|
+
const directive = input.handoff.stage_handoff.effective === "same_session" ? { kind: "continue_current_session", stage, prompt_path: promptPath, worker_prompt_markdown: prompt } : { kind: "await_new_session", stage, work_id: work.work_id, prompt_path: promptPath, resume_command: managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json`) };
|
|
71
72
|
return { directive, prompt_path: promptPath };
|
|
72
73
|
}
|
|
73
74
|
export function startVnextProtocolize(context, input) {
|
|
@@ -173,7 +174,7 @@ export function finishVnextProtocolize(context, input) {
|
|
|
173
174
|
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "protocol_documents_materialized", work_id: work.work_id, protocol_ids: protocolIds, stage });
|
|
174
175
|
advanceFlowRun(context, { settlementStage: "protocolize", projectRoot, runId: run.id, status: "running", verdict: "protocolized", nextAction: "start_plan" });
|
|
175
176
|
refreshRunWorkProjection(context, project.id, run.id);
|
|
176
|
-
const nextCommand = `${flowCommand(context)} stage start ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json
|
|
177
|
+
const nextCommand = managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage plan --project-root ${JSON.stringify(projectRoot)} --json`);
|
|
177
178
|
return { ok: true, outcome: "protocolized", run_id: run.id, protocol_ids: protocolIds, workspace_root: workspaceRoot, git_policy: resolvedPolicy, next_action: "await_plan", next_command: nextCommand, next: { kind: "start_stage", stage: "plan", command: nextCommand, cwd: workspaceRoot, session_mode: resolvedPolicy.stage_session_mode }, artifacts: { result: resultFile, report_json: reportJson, report_markdown: reportMarkdown, report_html: reportHtml, protocols: protocolIds.map((id) => path.join(workspaceRoot, ".memory-bank", "protocol", id, "summary.md")) } };
|
|
178
179
|
}
|
|
179
180
|
function materialize(projectRoot, runId, runHome, result, obligations, ids, psetId) {
|
|
@@ -441,7 +442,7 @@ function resultTemplate(obligations = []) {
|
|
|
441
442
|
}
|
|
442
443
|
function renderPrompt(input) {
|
|
443
444
|
const obligationList = input.obligations.map((obligation) => `- ${obligation.id}: ${obligation.statement}`).join("\n");
|
|
444
|
-
return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(), null, 2)}\n\`\`\``, "Allocate every supplied R-* and AC-* exactly once in obligation_ownership. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Run this exact one-command heredoc, replacing only its placeholder body. The heredoc is the permitted stdin form; do not use cat, a pipe, a temporary file or a second shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.
|
|
445
|
+
return ["<work_context>", `- RUN: ${input.runId}`, `- Work: ${input.workId}`, `- Accepted SPECIFY JSON: ${input.specifyPath}`, `- Handoff: ${input.handoff.stage_handoff.effective}`, "</work_context>", "", "<accepted_obligations>", obligationList, "</accepted_obligations>", "", "<frozen_git_policy>", `- route: ${input.policy.route}`, `- integration branch: ${input.policy.integration_branch ?? "not applicable"}`, `- base ref: ${input.policy.base_ref ?? "not applicable"}`, `- feature branch: ${input.policy.feature_branch ?? "not applicable"}`, `- service worktree: ${input.policy.worktree_path ?? "not applicable"}`, `- bootstrap: ${input.policy.bootstrap_status}`, `- decision: ${input.policy.reason}`, "The CLI already created and bootstrapped this workspace at PROTOCOLIZE start. Do not create branches, worktrees, protocol files or Git commits yourself. This transition Work may run from the stable session; edit only the supplied RUN result file.", "</frozen_git_policy>", "", "<catalog>", input.catalog.length ? input.catalog.map((x) => `- ${x}`).join("\n") : "- inactive", "</catalog>", "", "<stage_instructions>", input.template.trim(), "</stage_instructions>", "", "<output_contract>", `Edit only ${input.resultPath}. Keep this exact JSON shape; CLI allocates ids and writes PRT/PSET/feature documents in the already provisioned workspace:\n\n\`\`\`json\n${JSON.stringify(resultTemplate(), null, 2)}\n\`\`\``, "Allocate every supplied R-* and AC-* exactly once in obligation_ownership. The CLI rejects unknown, duplicate or missing obligations, and every member must own at least one AC-*.", "If PROTOCOLIZE itself exposes a material user decision with no reasonable default, do not finish and do not return to SPECIFY. Run this exact one-command heredoc, replacing only its placeholder body. The heredoc is the permitted stdin form; do not use cat, a pipe, a temporary file or a second shell command:", "```sh", input.pauseCommandTemplate, "```", "Ask the returned user_message and stop. Resume this same PROTOCOLIZE Work using the exact command returned by pause.", `When the answer is incorporated and the result is protocolized, finish:\n\n${input.finishCommand}`, "</output_contract>", ""].join("\n");
|
|
445
446
|
}
|
|
446
447
|
function provisionWorkspaceForProtocolize(context, projectRoot, projectId, run, stageRoot) {
|
|
447
448
|
const receiptPath = path.join(stageRoot, "workspace-route.json");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -83,7 +84,7 @@ export function launchVnextSpecify(context, input) {
|
|
|
83
84
|
};
|
|
84
85
|
writeJson(contextPath, workContext);
|
|
85
86
|
const pauseCommand = stagePauseCommand(context, { runId: run.id, stage: stageId, workId, projectRoot });
|
|
86
|
-
const prompt = renderPrompt({ workId, runId: run.id, projectRoot, stageRoot, resultPath, discussion, template, workContext, grounding, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand),
|
|
87
|
+
const prompt = renderPrompt({ workId, runId: run.id, projectRoot, stageRoot, resultPath, discussion, template, workContext, grounding, pauseCommand, pauseCommandTemplate: stagePauseCommandTemplate(pauseCommand), finishCommand: managedLifecycleCommand(context, `${flowCommand(context)} stage finish ${run.id} --project-root ${JSON.stringify(projectRoot)} --stage specify --result-file ${JSON.stringify(path.join(stageRoot, "specify-result.json"))} --outcome specified --json`) });
|
|
87
88
|
fs.writeFileSync(promptPath, prompt);
|
|
88
89
|
const externalContext = applyExternalStageContext({ stageRoot, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
|
|
89
90
|
context.db.run(`INSERT INTO works (work_id, project_id, run_id, parent_work_id, task, launch_policy, result_schema, depends_on_json, status, result, started_at, created_at, updated_at, completed_at)
|
|
@@ -190,7 +191,7 @@ export function submitVnextSpecify(context, input) {
|
|
|
190
191
|
const nextWork = requireWork(context, work.work_id);
|
|
191
192
|
advanceFlowRun(context, { settlementStage: "specify", projectRoot, runId: run.id, status: "running", verdict: "specified", nextAction: "start_protocolize" });
|
|
192
193
|
refreshRunWorkProjection(context, run.project_id, run.id);
|
|
193
|
-
const nextCommand = `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json
|
|
194
|
+
const nextCommand = managedLifecycleCommand(context, `${flowCommand(context)} stage start ${run.id} --stage protocolize --project-root ${JSON.stringify(projectRoot)} --json`);
|
|
194
195
|
return {
|
|
195
196
|
ok: true,
|
|
196
197
|
schema_id: "dd-flow/vnext-work-submit@1",
|
|
@@ -398,8 +399,8 @@ function renderPrompt(input) {
|
|
|
398
399
|
input.pauseCommandTemplate,
|
|
399
400
|
"```",
|
|
400
401
|
"The response tells you exactly what to ask and how to resume this same stage. Ask its user_message and stop the Turn.",
|
|
401
|
-
`Finish exactly once only after all questions are resolved. Run this as one standalone Bash command:\n\`${input.
|
|
402
|
-
"Do not combine the lifecycle command with cat, skill reads, Git commands, pipes or shell operators. If finish reports a validation error, correct the same result file and
|
|
402
|
+
`Finish exactly once only after all questions are resolved. Run this as one standalone Bash command:\n\`${input.finishCommand}\`.`,
|
|
403
|
+
"Do not combine the lifecycle command with cat, skill reads, Git commands, pipes or shell operators. If finish reports a validation error, correct the same result file and use its returned retry_command in this Session; only if no invocation-id was issued may you rerun the original command. After success, follow only its returned next directive; a focused eval controller may explicitly stop at this stage boundary.",
|
|
403
404
|
"</output_contract>",
|
|
404
405
|
""
|
|
405
406
|
].join("\n");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
1
2
|
import crypto from "node:crypto";
|
|
2
3
|
import fs from "node:fs";
|
|
3
4
|
import path from "node:path";
|
|
@@ -313,7 +314,7 @@ export function shortWorkId(id) { return /^WRK-\d{3,}(?:-|$)/.exec(id)?.[0]?.rep
|
|
|
313
314
|
* PATH is not inherited; using the shared lifecycle command keeps them on the
|
|
314
315
|
* same captured runtime as their parent stage.
|
|
315
316
|
*/
|
|
316
|
-
export function workStartCommand(context, work) { const run = requireRun(context, work.project_id, work.run_id); const recovery = recoveryGuard(context, work.project_id, work.run_id); return `${flowCommand(context)} work start ${work.work_id} --project-root ${JSON.stringify(run.project_root)}${recovery ? ` --recovery-id ${JSON.stringify(recovery.recovery_id)}` : ""} --json
|
|
317
|
+
export function workStartCommand(context, work) { const run = requireRun(context, work.project_id, work.run_id); const recovery = recoveryGuard(context, work.project_id, work.run_id); return managedLifecycleCommand(context, `${flowCommand(context)} work start ${work.work_id} --project-root ${JSON.stringify(run.project_root)}${recovery ? ` --recovery-id ${JSON.stringify(recovery.recovery_id)}` : ""} --json`); }
|
|
317
318
|
export function assertWorkLaunchReady(context, id) {
|
|
318
319
|
ensureWorkRegistry(context);
|
|
319
320
|
const work = requireWork(context, id);
|
|
@@ -780,19 +781,21 @@ export function validateReviewResolutions(workId, assigned, resolutions) {
|
|
|
780
781
|
function renderWorkerPrompt(context, work, run, dependencies) {
|
|
781
782
|
const command = flowCommand(context);
|
|
782
783
|
const packet = codePacket(work);
|
|
784
|
+
const finishWorkCommand = managedLifecycleCommand(context, `${command} work finish ${work.work_id} --result-stdin --project-root ${JSON.stringify(run.project_root)} --json --progress-jsonl`);
|
|
785
|
+
const failWorkCommand = managedLifecycleCommand(context, `${command} work fail ${work.work_id} --reason "receipt path + exact external or semantic blocker" --project-root ${JSON.stringify(run.project_root)} --json`);
|
|
783
786
|
const mergeWork = parsePayload(work)?.kind === "merge";
|
|
784
787
|
const writeBoundary = mergeWork
|
|
785
788
|
? ["MERGE does not own product-code repair. Use only the stage packet's merge apply command; resolve only paths that Git reports as unmerged. Do not edit integration code, tests, documentation or configuration to make a gate pass.", "RUN artifacts are read-only evidence: refer to them with run:// URIs and let dd-flow persist your submitted result."]
|
|
786
789
|
: [`HARD RULE: project source reads and writes must remain under ${run.workspace_root}.`, "Do not write through project_root, outside workspace_root, into another RUN, or into Git/worktree control data. Do not create, switch, merge or delete branches/worktrees.", "RUN artifacts are read-only evidence: refer to them with run:// URIs and let dd-flow persist your submitted result. Accepted requirements, non-goals and stop_conditions are semantic hard boundaries. planned_write_areas is not."];
|
|
787
790
|
const completionRepair = mergeWork
|
|
788
791
|
? ["A failed integration check is evidence for a source-repair cycle, not permission to repair the integration workspace. Follow the exact merge repair command returned by dd-flow."]
|
|
789
|
-
: ["Read the failed receipt and its stdout/stderr. Fix project-owned source, migration, test, formatting, or configuration errors in this same Work, then
|
|
792
|
+
: ["Read the failed receipt and its stdout/stderr. Fix project-owned source, migration, test, formatting, or configuration errors in this same Work, then use the returned retry_command with your result. If the command has no invocation-id, repeat the original Finish. Do not invent a cause that does not appear in the retained output."];
|
|
790
793
|
const codeContext = packet ? ["<semantic_spine>", JSON.stringify(packet.semantic_spine, null, 2), "</semantic_spine>", "", ...(packet.repair ? ["<repair_context>", JSON.stringify(packet.repair, null, 2), "Read the failed receipt and its linked stdout/stderr before editing. Preserve the accepted origin context and fix only the evidenced failure.", "</repair_context>", ""] : []), "<accepted_requirements>", JSON.stringify(packet.requirements, null, 2), "</accepted_requirements>", "", "<write_boundary_invariant>", "For a mutation guarded by membership, ownership, authorization or parent lifecycle state, preserve that predicate in the write statement or make guard and write one explicit transaction with the needed lock. A prior read may diagnose an error but never proves a later write remains allowed. Apply this to create, update, delete and parent-state mutations.", "</write_boundary_invariant>", "", "<acceptance_context>", "The criteria below are end-to-end context. Complete this Work's semantic contribution and declared checks; another ordered Work may own a different acceptance surface.", JSON.stringify(packet.acceptance, null, 2), "</acceptance_context>", "", "<required_read>", "These are mandatory starting sources, not a read allowlist. Read any additional project files needed to implement the Work correctly.", ...packet.required_read.map((item) => `- ${resolveRunReferences(item, work.run_id, requireRunHome(run))}`), "</required_read>", "", "<discovery_boundary>", "These are likely discovery areas, not a hard boundary. Expand project-local investigation when required and report material additions.", ...packet.discovery_boundary.map((item) => `- ${item}`), "</discovery_boundary>", "", "<planned_write_areas>", "SOFT COORDINATION HINT ONLY. These paths help the coordinator avoid concurrent collisions. They do not grant or deny write permission and do not limit the files needed for this Work. You may create or change any project file under workspace_root that is necessary and in semantic scope; report every actual changed path.", ...(packet.planned_write_areas.length ? packet.planned_write_areas.map((item) => `- ${item}`) : ["- none predicted; derive the necessary files from the task"]), "</planned_write_areas>", "", ...(packet.provides_checks.length ? ["<provided_checks>", ...packet.provides_checks.map((item) => `- ${item.id}: materialize ${item.command}${item.definition ? ` as ${item.definition}` : ""}; it is not usable until this Work finishes.`), "Update the declared project command or alias before Work finish. The CLI verifies materialization at Work finish. Execution occurs at its declared run_at gate, not necessarily in this Work.", "</provided_checks>", ""] : []), "<verification>", ...packet.checks.map((item) => `- ${item.id} at ${item.run_at}: ${item.command} — ${item.purpose}`), "The CLI executes work-scoped checks and retains their receipts. Report semantic evidence only; do not rerun declared checks manually.", "</verification>", "", "<stop_conditions>", ...packet.stop_conditions.map((item) => `- ${item}`), "</stop_conditions>", ""] : [];
|
|
791
794
|
if (packet)
|
|
792
795
|
codeContext.push("<document_updates>", JSON.stringify(packet.document_updates, null, 2), "Materialize every listed update. dd-flow verifies the resulting file against its PLAN-time baseline.", "</document_updates>", "", "<completion_contract>", "Successful completion requires empty deviations and blockers and every assigned document update in changed_paths. A necessary path outside planned_write_areas is normal coordination drift, not a blocker; include it in changed_paths and continue.", "</completion_contract>", "");
|
|
793
796
|
if (packet)
|
|
794
797
|
codeContext.push("<temporary_services>", "Prefer the declared check launcher: it already owns check resources. If the planned scenario genuinely requires an interactive HTTP service, use the managed supervisor below. This is a template: replace the project service command, port names and readiness path from the plan/project instructions; do not invent a fixed port.", `${command} runtime process start --run ${run.id} --project-root ${JSON.stringify(run.project_root)} --command '<project-service-command>' --ports api --ready-port api --ready-path /health --json --progress-jsonl`, "The service receives DD_FLOW_PORT_API (and equivalent variables for all declared names). The command stays running as its supervisor. Retain its tool handle; wait for the service ready event and read its service.json receipt. Pass those exact ports and the same project environment to reset/seed, API and browser operations.", "A ready receipt proves service readiness only. Record the scenario outcome and real evidence separately. After the scenario, execute the exact stop_command from that receipt, then wait for the supervisor to exit. Never use pkill/killall or stop a sibling's process. If cleanup fails, retain the process id and report the failure; do not claim the resource is free.", "</temporary_services>", "");
|
|
795
|
-
return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<hard_write_boundary>", ...writeBoundary, "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), "Do not create result.json yourself. Send the JSON to dd-flow on stdin; it atomically validates and stores the canonical receipt.", "</result_contract>", ""] : []), "<completion>", packet?.repair?.verification_check_refs?.length ? "Work finish runs its normal work-scoped checks plus the listed causal repair checks. Their original run_at remains an aggregate obligation; this is the additional proof required before accepting this repair." : "Work finish runs only declared run_at=work checks. Stage finish owns readiness/code/merge gates; successful Work completion does not mean those gates have passed.", "A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", ...completionRepair, "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `Finish as one standalone command
|
|
798
|
+
return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<hard_write_boundary>", ...writeBoundary, "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), "Do not create result.json yourself. Send the JSON to dd-flow on stdin; it atomically validates and stores the canonical receipt.", "</result_contract>", ""] : []), "<completion>", packet?.repair?.verification_check_refs?.length ? "Work finish runs its normal work-scoped checks plus the listed causal repair checks. Their original run_at remains an aggregate obligation; this is the additional proof required before accepting this repair." : "Work finish runs only declared run_at=work checks. Stage finish owns readiness/code/merge gates; successful Work completion does not mean those gates have passed.", "A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", ...completionRepair, "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `Finish as one standalone command with a quoted heredoc (replace the example JSON with your result):\n${finishWorkCommand} <<'DD_FLOW_RESULT'\n{}\nDD_FLOW_RESULT`, `Fail only for an evidenced external or semantic-contract blocker: ${failWorkCommand}`, "</completion>", ""].join("\n");
|
|
796
799
|
}
|
|
797
800
|
export function resultSchemaGuidance(work, runId) {
|
|
798
801
|
const schema = work.result_schema;
|