@deksden-com/dd-flow-cli 0.9.0-beta.122 → 0.9.0-beta.123

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.
@@ -30,7 +30,7 @@ import { requireRun, requireController, controllerReceipt, appendEvent, saveStat
30
30
  import { assertNoCorrelatedRejection } from "./continuation-outcome.js";
31
31
  import { spawnController, recordControllerLauncher, claimControllerProcess, markControllerDispatchReady } from "./run-controller-process.js";
32
32
  import { controllerAdapter, controllerEnvironment, sessionArguments } from "./run-controller-adapter.js";
33
- import { continueCodexCapacity } from "./codex-capacity.js";
33
+ import { capacityDelay, continueCodexCapacity } from "./codex-capacity.js";
34
34
  import { resumeControllerSession, failControllerRecovery } from "./run-controller-recovery.js";
35
35
  import { captureControllerBoundary } from "./run-controller-capture.js";
36
36
  import { requestRunControl } from "./run-control-worker.js";
@@ -534,8 +534,15 @@ async function executeController(context, row, manifest, state, assertOwnership,
534
534
  await reconcilePromptPredecessor(session);
535
535
  const firstOperationId = operationId ?? `${row.controller_id}:prompt:${crypto.randomUUID()}`;
536
536
  const send = (value, id) => {
537
+ const retained = context.db.get("SELECT input_json FROM run_controller_operations WHERE operation_id = ? AND controller_id = ? AND generation = ?", [id, row.controller_id, row.generation]);
538
+ if (retained) {
539
+ const dispatch = JSON.parse(retained.input_json);
540
+ if (![value, `${value}\n`].some(text => dispatch.prompt_sha256 === crypto.createHash("sha256").update(text).digest("hex")))
541
+ throw new AppError("capacity_continuation_conflict", "Retained prompt differs from continuation", 1);
542
+ return controllerAdapter(executionContext, row, manifest, session, dispatch.args, assertOwner, id, cancellation.signal);
543
+ }
537
544
  const file = path.join(row.state_dir, `turn-${String(state.turns + 1).padStart(5, "0")}.md`);
538
- fs.writeFileSync(file, `${value}\n`, { flag: "wx", mode: 0o600 });
545
+ fs.writeFileSync(file, value, { flag: "wx", mode: 0o600 });
539
546
  state.turns += 1;
540
547
  saveState(context, row, state);
541
548
  return controllerAdapter(executionContext, row, manifest, session, ["session", "prompt", "--session-id", session.id, "--prompt-file", file, ...sessionArguments(context, manifest, session)], assertOwner, id, cancellation.signal);
@@ -553,24 +560,45 @@ async function executeController(context, row, manifest, state, assertOwnership,
553
560
  throw error;
554
561
  const inspect = (args, id) => runHarnessAdapter({ executable: manifest.adapters["codex-desktop"].executable,
555
562
  args, env: controllerEnvironment(context, manifest, session), runtimeOwner: session.owner, operationId: id, timeoutMs: 30_000, signal: cancellation.signal });
556
- const resumed = await continueCodexCapacity({ error, sessionId: session.id, operationId: firstOperationId,
563
+ const resumed = await continueCodexCapacity({ error, sessionId: session.id, operationId: firstOperationId, signal: cancellation.signal, allowSettledDescendants: true,
564
+ retainedIntents: () => context.db.all("SELECT receipt_json FROM run_controller_operations WHERE controller_id = ? AND generation = ? AND operation_id IN (?, ?)", [row.controller_id, row.generation, firstOperationId, `${firstOperationId}:capacity:1:session.prompt`])
565
+ .flatMap(item => { const retained = JSON.parse(item.receipt_json || "null"); return retained?.capacity_intent ? [retained.capacity_intent] : []; }).sort((a, b) => a.ordinal - b.ordinal),
557
566
  inspectOperation: id => inspect(["daemon", "operation", "--state-dir", session.state_dir, "--operation-id", id, "--json"], `${id}:capacity-observe`),
558
- inspectSession: () => inspect(["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], `${firstOperationId}:capacity-inspect`),
567
+ inspectSession: turnId => inspect(["session", "inspect", "--session-id", session.id, ...(turnId ? ["--turn-id", turnId] : []), ...sessionArguments(context, manifest, session)], `${firstOperationId}:capacity-inspect`),
559
568
  assertCurrent: () => {
560
569
  assertOwner();
570
+ },
571
+ decision: async () => {
572
+ if (kind === "child") {
573
+ const target = Object.entries(state.child_capacity ?? {}).find(([, retained]) => retained.operation_id === firstOperationId);
574
+ if (!target)
575
+ return "reconciliation_blocked";
576
+ const seen = await inspect(["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], `${firstOperationId}:child-followup-observe`);
577
+ const child = controllerNativeChildren(seen, session.id).find(item => item.session_id === target[0]);
578
+ if (child?.parent_session_id === session.id && (child.status === "running" || child.terminal_turn_id && child.terminal_turn_id !== target[1].turn_id))
579
+ return "completed";
580
+ }
561
581
  const view = getFlowRunStatus(executionContext, { projectRoot: manifest.request.project_root, runId: row.run_id });
562
582
  const next = view.continuation;
583
+ if (next.kind === "blocked")
584
+ return "reconciliation_blocked";
585
+ if (next.kind === "paused" && next.stage !== state.stage)
586
+ return "paused";
587
+ if (next.kind === "terminal" || next.stage !== state.stage)
588
+ return "completed";
563
589
  if (hitl) {
564
590
  const answerId = firstOperationId.slice(0, -`:prompt:${row.generation}`.length);
565
591
  const answer = context.db.get("SELECT input_json FROM run_controller_operations WHERE operation_id = ? AND controller_id = ? AND status = 'accepted'", [answerId, row.controller_id]);
566
592
  const accepted = answer && JSON.parse(answer.input_json);
567
593
  const pause = view.index.stage_runs.find(item => item.stage === accepted?.stage)?.pause;
568
- if (next.kind !== "paused" || next.stage !== state.stage || !accepted || pause?.id !== accepted.pause_id || pause.work_id !== accepted.work_id || pause.answer_path)
569
- throw new AppError("capacity_continuation_blocked", "Accepted HITL pause changed during capacity wait", 1, { continuation: next });
570
- }
571
- else if (next.stage !== state.stage || ["terminal", "paused", "blocked", "wait"].includes(next.kind)) {
572
- throw new AppError("capacity_continuation_blocked", "RUN changed while native capacity was unavailable", 1, { continuation: next });
594
+ if (next.kind !== "paused")
595
+ return "continue";
596
+ if (!accepted || pause?.id !== accepted.pause_id || pause.work_id !== accepted.work_id || pause.answer_path)
597
+ return "paused";
573
598
  }
599
+ else if (next.kind === "paused")
600
+ return "paused";
601
+ return next.kind === "wait" ? "reconciliation_blocked" : "continue";
574
602
  },
575
603
  retainIntent: intent => context.db.writeTransaction(() => {
576
604
  const changed = context.db.run("UPDATE run_controller_operations SET status = 'failed', receipt_json = ?, updated_at = ? WHERE operation_id = ? AND controller_id = ? AND generation = ? AND status = 'outcome_unknown'", [JSON.stringify({ capacity_intent: intent }), context.now(), intent.predecessor, row.controller_id, row.generation]);
@@ -579,7 +607,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
579
607
  appendEvent(context, row.controller_id, "capacity_continuation", intent);
580
608
  }),
581
609
  prompt: (id, value) => send(value, id),
582
- ...(resumeCommand ? { continuationText: `The preceding Turn failed before any tool call. Continue the same accepted HITL answer in this Session. If the pause is still current, run this exact previously issued standalone command once without changing the answer:\n\n${resumeCommand}\n\nDo not accept or deliver the answer again if the pause has changed.` } : {}) });
610
+ ...(resumeCommand ? { continuationText: `Continue the same accepted HITL task in this Session. First inspect current durable RUN and pause state. Only if the exact pause still awaits the accepted answer, run this previously issued command once:\n\n${resumeCommand}\n\nNever replay an already committed answer. Otherwise use only the current issued successor.` } : {}) });
583
611
  if (!resumed)
584
612
  throw error;
585
613
  receipt = resumed;
@@ -773,18 +801,22 @@ async function executeController(context, row, manifest, state, assertOwnership,
773
801
  const prior = child && state.child_capacity?.[child.session_id];
774
802
  const links = child ? context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: "codex-desktop", session_id: child.session_id })]) : [];
775
803
  if (child && turnId && native && terminalCodexOverload(native, child.session_id, turnId)
776
- && child.native_turn_items?.observed === true && child.native_turn_items.possible_effects === false
804
+ && child.native_turn_items?.observed === true && (child.native_turn_items.possible_effects === false || child.native_turn_items.pending === false)
777
805
  && inspected.settled === true && child.parent_session_id === session.id && links.length <= 1
778
- && (!links.length || links[0].status === "running") && (prior?.attempts ?? 0) < 2) {
779
- if (prior?.turn_id === turnId)
806
+ && (!links.length || links[0].status === "running") && ((prior?.attempts ?? 0) < 2 || prior?.turn_id === turnId && prior.dispatched === false)) {
807
+ const prepared = prior?.turn_id === turnId && prior.dispatched === false && prior.operation_id && prior.not_before ? prior : null;
808
+ if (prior?.turn_id === turnId && !prepared)
780
809
  throw new AppError("capacity_child_followup_unconfirmed", "Parent continuation did not start a new native child Turn", 1, { child_session_id: child.session_id, turn_id: turnId });
781
- const attempt = (prior?.attempts ?? 0) + 1;
810
+ const attempt = prepared ? prepared.attempts : (prior?.attempts ?? 0) + 1;
811
+ const wait = prepared ? Math.max(0, Date.parse(prepared.not_before) - Date.now()) : capacityDelay(native, attempt);
812
+ if (!Number.isFinite(wait) || wait > 30_000)
813
+ throw new AppError("capacity_child_retry_delay_exceeded", "Native child retry delay exceeds the bounded continuation window", 1, { child_session_id: child.session_id, delay_ms: wait });
814
+ const operationId = prepared?.operation_id ?? `${row.controller_id}:child:${digest([child.session_id, turnId]).slice(0, 16)}:followup:${attempt}`;
782
815
  state.child_capacity ??= {};
783
- state.child_capacity[child.session_id] = { turn_id: turnId, attempts: attempt };
816
+ state.child_capacity[child.session_id] = { turn_id: turnId, attempts: attempt, operation_id: operationId, not_before: prepared?.not_before ?? new Date(Date.now() + wait).toISOString(), dispatched: false };
784
817
  saveState(context, row, state);
785
- const operationId = `${row.controller_id}:child:${digest([child.session_id, turnId]).slice(0, 16)}:followup:${attempt}`;
786
818
  appendEvent(context, row.controller_id, "capacity_child_followup", { child_session_id: child.session_id, turn_id: turnId, attempt, operation_id: operationId });
787
- await delay(attempt === 1 ? 5_000 : 15_000);
819
+ await delay(wait, undefined, { signal: cancellation.signal });
788
820
  assertOwner();
789
821
  const current = getFlowRunStatus(executionContext, { projectRoot: manifest.request.project_root, runId: row.run_id });
790
822
  if (current.continuation.stage !== state.stage || ["terminal", "paused", "blocked", "wait"].includes(current.continuation.kind))
@@ -792,13 +824,20 @@ async function executeController(context, row, manifest, state, assertOwnership,
792
824
  const fresh = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
793
825
  const still = controllerNativeChildren(fresh, session.id).find(item => item.session_id === child.session_id);
794
826
  const currentLinks = context.db.all("SELECT w.status FROM work_sessions ws JOIN works w ON w.work_id = ws.work_id WHERE w.project_id = ? AND w.run_id = ? AND ws.session_id = ?", [row.project_id, row.run_id, storageSessionId({ harness_id: "codex-desktop", session_id: child.session_id })]);
827
+ if (still?.status === "completed" && currentLinks.length <= 1 && currentLinks.every(link => link.status === "completed")) {
828
+ state.last_receipt = fresh;
829
+ saveState(context, row, state);
830
+ continue;
831
+ }
795
832
  if (fresh.settled !== true || !still || still.parent_session_id !== session.id || still.status !== "failed" || still.terminal_turn_id !== turnId
796
- || still.native_turn_items?.observed !== true || still.native_turn_items.possible_effects !== false || currentLinks.length > 1
833
+ || still.native_turn_items?.observed !== true || still.native_turn_items.possible_effects !== false && still.native_turn_items.pending !== false || currentLinks.length > 1
797
834
  || currentLinks.some(link => link.status !== "running"))
798
835
  throw new AppError("capacity_child_followup_blocked", "Native child or Work changed during capacity wait", 1, { child_session_id: child.session_id, turn_id: turnId });
799
836
  assertOwner();
800
- const text = `The existing native child ${child.session_id} has a confirmed effect-free failed Turn. Use the native followup_task tool exactly once for that same child ID with this short message: "Continue your assigned Work from its current durable state. Do not repeat an observed lifecycle command. Finish only the original Work." Do not spawn another child, claim another Work, or perform the child's Work in this parent Turn. End this Turn after the followup_task response.`;
837
+ const text = `The existing native child ${child.session_id} has a confirmed terminal overloaded Turn. Use the native followup_task tool exactly once for that same child ID with this short message: "Continue your assigned Work from its current durable state. Do not repeat an observed lifecycle command. Finish only the original Work." Do not spawn another child, claim another Work, or perform the child's Work in this parent Turn. End this Turn after the followup_task response.`;
801
838
  await prompt(session, text, operationId, "child");
839
+ state.child_capacity[child.session_id].dispatched = true;
840
+ saveState(context, row, state);
802
841
  const after = await call(session, ["session", "inspect", "--session-id", session.id, ...sessionArguments(context, manifest, session)], false);
803
842
  state.last_receipt = after;
804
843
  saveState(context, row, state);
@@ -829,7 +868,7 @@ async function executeController(context, row, manifest, state, assertOwnership,
829
868
  const results = await Promise.allSettled(fanout.works.map(async (work) => {
830
869
  try {
831
870
  assertOwner();
832
- const receipt = await launchExternalWork(executionContext, { workId: work.work_id, stage: next.stage });
871
+ const receipt = await launchExternalWork(executionContext, { workId: work.work_id, stage: next.stage, signal: cancellation.signal });
833
872
  if (fatal)
834
873
  throw fatal;
835
874
  appendEvent(context, row.controller_id, "external_work_observed", { work_id: work.work_id, receipt });
@@ -90,7 +90,7 @@ export function runObservations(context, projectId, runId) {
90
90
  const actual = fs.realpathSync(file);
91
91
  if (!inside(home, actual))
92
92
  throw new Error("journal escaped owned home");
93
- fd = fs.openSync(actual, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
93
+ fd = fs.openSync(actual, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW | fs.constants.O_NONBLOCK);
94
94
  if (!fs.fstatSync(fd).isFile())
95
95
  throw new Error("journal is not a regular file");
96
96
  replayToolJournal(file, { harness: source.session.harness_id, rootSessionId: sessionId, text: fs.readFileSync(fd, "utf8"), ...(source.session.harness_id === "codex-desktop" ? { expectedSessionIds: expectedCodexIds } : {}) }, reducer);
@@ -114,7 +114,9 @@ export function runObservations(context, projectId, runId) {
114
114
  // Select transcript fallback only after replay reveals an actual gap.
115
115
  for (const session of sessions) {
116
116
  const nativeId = session.provider_session_id ?? session.session_id;
117
- if (!session.transcript_path || covered().has(JSON.stringify({ harness_id: session.harness, session_id: nativeId })))
117
+ const sessionSummary = reducer.summary(nativeId);
118
+ if (!session.transcript_path || covered().has(JSON.stringify({ harness_id: session.harness, session_id: nativeId }))
119
+ && sessionSummary.completeness === "complete" && sessionSummary.outcome_completeness === "complete")
118
120
  continue;
119
121
  const id = `transcript:${session.harness}:${nativeId}`;
120
122
  add(id, nativeId, session.transcript_path, home, "current", session.harness);
@@ -136,6 +138,9 @@ export function runObservations(context, projectId, runId) {
136
138
  if (!expectedIds.has(JSON.stringify(event.session))) {
137
139
  reducer.turns.delete(key);
138
140
  }
141
+ for (const [key, event] of reducer.sources)
142
+ if (!expectedIds.has(JSON.stringify(event.session)))
143
+ reducer.sources.delete(key);
139
144
  const summary = reducer.summary();
140
145
  const observed = new Set(summary.sessions.map(s => JSON.stringify(s.session)));
141
146
  const missing = expected.filter(s => !observed.has(JSON.stringify(s)));
@@ -39,6 +39,7 @@ export function acceptStagePacket(context, input) {
39
39
  context.db.afterCommit(() => publishStagePacket(context, input.projectId, input.runId, input.stage, input.prepared.file));
40
40
  }
41
41
  export function publishStagePacket(context, projectId, runId, stage, file) {
42
+ file = resolveStagePublicationPath(context, projectId, runId, file);
42
43
  assertPublicationPath(context, projectId, runId, file);
43
44
  fs.mkdirSync(path.dirname(file), { recursive: true });
44
45
  withProjectionLock(`${file}.publish-lock`, () => {
@@ -47,7 +48,7 @@ export function publishStagePacket(context, projectId, runId, stage, file) {
47
48
  const index = run ? JSON.parse(run.index_json) : null;
48
49
  const currentAttempt = index?.stage_runs?.find((item) => item.stage === stage)?.attempt;
49
50
  const rows = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_packet' ORDER BY id DESC", [projectId]);
50
- const packet = rows.map((row) => JSON.parse(row.payload_json)).find((item) => item.run_id === runId && item.stage === stage && item.attempt === currentAttempt && item.file === file);
51
+ const packet = rows.map((row) => JSON.parse(row.payload_json)).find((item) => item.run_id === runId && item.stage === stage && item.attempt === currentAttempt && resolveStagePublicationPath(context, projectId, runId, item.file) === file);
51
52
  if (!packet)
52
53
  return;
53
54
  const bytes = Buffer.from(packet.base64, "base64");
@@ -78,6 +79,7 @@ export function acceptStageReport(context, input) {
78
79
  }
79
80
  /** Replays the latest committed receipt, never an obsolete attempt. */
80
81
  export function publishStageReport(context, projectId, runId, stage, root) {
82
+ root = resolveStagePublicationPath(context, projectId, runId, root);
81
83
  assertPublicationPath(context, projectId, runId, root);
82
84
  withProjectionLock(`${root}.report-publish-lock`, () => {
83
85
  assertPublicationPath(context, projectId, runId, root);
@@ -85,7 +87,7 @@ export function publishStageReport(context, projectId, runId, stage, root) {
85
87
  const index = run ? JSON.parse(run.index_json) : null;
86
88
  const currentAttempt = index?.stage_runs?.find((item) => item.stage === stage)?.attempt;
87
89
  const events = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.stage_report' ORDER BY id DESC", [projectId]);
88
- const receipt = events.map((row) => JSON.parse(row.payload_json)).find((item) => item.run_id === runId && item.stage === stage && item.root === root && item.attempt === currentAttempt);
90
+ const receipt = events.map((row) => JSON.parse(row.payload_json)).find((item) => item.run_id === runId && item.stage === stage && item.attempt === currentAttempt && resolveStagePublicationPath(context, projectId, runId, item.root) === root);
89
91
  if (!receipt)
90
92
  return;
91
93
  fs.mkdirSync(root, { recursive: true });
@@ -104,6 +106,27 @@ export function publishStageReport(context, projectId, runId, stage, root) {
104
106
  }
105
107
  });
106
108
  }
109
+ /** Operational destinations follow validated imports, while audit bytes retain
110
+ * their original locators. SQL-only so readers may also use it in settlement. */
111
+ export function resolveStagePublicationPath(context, projectId, runId, locator) {
112
+ const run = context.db.get("SELECT run_root FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
113
+ if (!run?.run_root || !path.isAbsolute(locator))
114
+ throw new AppError("stage_packet_invalid", "Stage publication has no absolute bound RUN locator", 1, { run_id: runId, locator });
115
+ let destination = path.resolve(locator);
116
+ const mappings = context.db.all("SELECT payload_json FROM audit_events WHERE project_id = ? AND event_type = 'flow_run.publication_relocation' ORDER BY id", [projectId]);
117
+ for (const row of mappings) {
118
+ const mapping = JSON.parse(row.payload_json);
119
+ if (mapping.run_id !== runId || !path.isAbsolute(mapping.from) || !path.isAbsolute(mapping.to))
120
+ continue;
121
+ const relative = path.relative(mapping.from, destination);
122
+ if (relative && relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative))
123
+ destination = path.resolve(mapping.to, relative);
124
+ }
125
+ const relative = path.relative(run.run_root, destination);
126
+ if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative))
127
+ throw new AppError("publication_pending", "Stage publication escaped its bound RUN lineage", 1, { effect: "committed", run_id: runId, locator });
128
+ return destination;
129
+ }
107
130
  /** Recheck the actual destination after preparation: a missing leaf may have
108
131
  * an existing symlink ancestor, and retained packet bytes never authorize it. */
109
132
  function assertPublicationPath(context, projectId, runId, file) {
@@ -4,13 +4,18 @@ import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
5
  import { vnextStageDirectory } from "../domain/stage-catalog.js";
6
6
  import { checkReceiptEvidenceValid, checkReceipts, effectiveCheckDeclarations } from "./code-checks.js";
7
- import { acceptStagePacket, prepareStagePacket, publishStagePacket } from "./stage-report-renderer.js";
7
+ import { acceptStagePacket, prepareStagePacket, publishStagePacket, resolveStagePublicationPath } from "./stage-report-renderer.js";
8
8
  import { assertRunMutationAllowed } from "./run-recovery.js";
9
9
  export const verificationMatrixContract = "dd-flow/verification-matrix@1";
10
10
  /** A view of already validated facts: never a gate or a receipt selector. */
11
11
  export function buildVerificationMatrix(input) {
12
12
  const sourceCatalog = new Map();
13
+ const pathChecksums = new Map();
13
14
  for (const source of input.sources) {
15
+ const location = JSON.stringify([source.root, source.path]);
16
+ if (pathChecksums.has(location) && pathChecksums.get(location) !== source.sha256)
17
+ throw new AppError("verification_matrix_source_changed", "Matrix source aliases have conflicting checksums", 1, { root: source.root, path: source.path });
18
+ pathChecksums.set(location, source.sha256);
14
19
  const identity = JSON.stringify([source.role, source.root, source.path]);
15
20
  const prior = sourceCatalog.get(identity);
16
21
  if (prior && prior.sha256 !== source.sha256)
@@ -105,9 +110,24 @@ export function prepareRunVerificationMatrix(context, input) {
105
110
  const ownership = Object.fromEntries(protocols.map((protocol, i) => [protocol, protocolize.obligation_ownership.filter(item => item.member_keys.includes(protocolize.delivery.members[i].key)).map(item => item.obligation_id)]));
106
111
  const skeleton = input.stage === "plan" && input.role === "input";
107
112
  const plans = [];
108
- const predecessor = input.stage === "plan-review" ? "plan" : input.stage === "code" ? "plan-review" : input.stage === "code-review" ? "code" : input.stage === "merge" ? "code-review" : null;
109
- const priorMatrix = input.role === "input" && predecessor ? latestRunVerificationMatrix(context, input.projectId, input.runId, predecessor) : null;
113
+ const reviewSkipped = index.settings?.code_review?.mode === "off" && Boolean(index.settings.code_review.reason?.trim()) && !index.stage_runs?.some(item => item.stage === "code-review");
114
+ const predecessor = input.stage === "plan-review" ? "plan" : input.stage === "code" ? "plan-review" : input.stage === "code-review" ? "code" : input.stage === "merge" ? reviewSkipped ? "code" : "code-review" : null;
115
+ const priorMatrix = input.role === "input" && predecessor ? latestRunVerificationMatrix(context, input.projectId, input.runId, predecessor, "output") : null;
116
+ if (input.role === "input" && predecessor && !priorMatrix)
117
+ throw new AppError("verification_matrix_packet_missing", "Marked Stage requires its accepted predecessor output matrix", 1, { stage: input.stage, predecessor });
110
118
  if (!skeleton) {
119
+ const batchPath = "03-plan/code-work-batch.json", batchFile = safePath(home, batchPath);
120
+ if (!fs.existsSync(batchFile))
121
+ throw new AppError("verification_matrix_source_missing", "Marked matrix requires its generated CODE batch", 1, { path: batchPath });
122
+ const batchBytes = fs.readFileSync(batchFile), batchSha = digest(batchBytes);
123
+ const acceptedBatch = priorMatrix?.matrix.sources.find(source => source.role === "code_work_batch");
124
+ if (acceptedBatch && acceptedBatch.sha256 !== batchSha)
125
+ throw new AppError("verification_matrix_source_changed", "CODE batch differs from accepted predecessor matrix", 1, { path: batchPath, expected: acceptedBatch.sha256, current: batchSha });
126
+ const batchSnapshot = `${vnextStageDirectory(input.stage)}/verification/sources/${batchSha}/code-work-batch.json`;
127
+ packets.push(prepareStagePacket(safePath(home, batchSnapshot), batchBytes));
128
+ sources.push({ role: "code_work_batch", root: "run", path: batchSnapshot, sha256: batchSha });
129
+ if (input.stage === "merge" && input.role === "output")
130
+ sources.push({ role: "code_work_batch_current", root: "run", path: batchPath, sha256: batchSha });
111
131
  for (const protocol of protocols) {
112
132
  const relative = `.memory-bank/protocol/${protocol}/plan.json`, file = safePath(workspace, relative);
113
133
  // PLAN bytes are retained before MERGE cleanup; later recovery does not
@@ -266,9 +286,9 @@ export function latestRunVerificationMatrix(context, projectId, runId, stage, ro
266
286
  continue;
267
287
  if (matrix.schema_id !== verificationMatrixContract || matrix.header.run_id !== runId || matrix.header.stage !== stage || matrix.header.stage_attempt !== attempt)
268
288
  throw new AppError("stage_packet_invalid", "Retained matrix owner differs from its packet", 1);
269
- safePath(run.run_root, path.relative(run.run_root, packet.file), false);
289
+ packet.file = resolveStagePublicationPath(context, projectId, runId, packet.file);
270
290
  const mdFile = packet.file.replace(/\.json$/, ".md");
271
- const paired = rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.stage === stage && item.attempt === attempt && item.file === mdFile);
291
+ const paired = rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.stage === stage && item.attempt === attempt && resolveStagePublicationPath(context, projectId, runId, item.file) === mdFile);
272
292
  if (!paired)
273
293
  throw new AppError("stage_packet_invalid", "Retained matrix lacks its exact Markdown packet", 1);
274
294
  const mdBytes = Buffer.from(paired.base64, "base64");
@@ -277,9 +297,9 @@ export function latestRunVerificationMatrix(context, projectId, runId, stage, ro
277
297
  const md = { file: mdFile, bytes: mdBytes, sha256: paired.sha256 };
278
298
  const json = { file: packet.file, bytes, sha256: packet.sha256 };
279
299
  const metadata = { contract: verificationMatrixContract, role: matrix.header.role, completeness: matrix.header.completeness, source_fingerprint: matrix.header.source_fingerprint, json: { path: slash(path.relative(run.run_root, packet.file)), sha256: packet.sha256 }, markdown: { path: slash(path.relative(run.run_root, mdFile)), sha256: md.sha256 } };
280
- const sourcePackets = matrix.sources.filter(source => source.role.startsWith("plan:") && source.root === "run").map(source => {
300
+ const sourcePackets = matrix.sources.filter(source => (source.role.startsWith("plan:") || source.role === "code_work_batch") && source.root === "run").map(source => {
281
301
  const file = safePath(run.run_root, source.path, false);
282
- const retained = rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.stage === stage && item.attempt === attempt && item.file === file && item.sha256 === source.sha256);
302
+ const retained = rows.map(row => JSON.parse(row.payload_json)).find(item => item.run_id === runId && item.stage === stage && item.attempt === attempt && resolveStagePublicationPath(context, projectId, runId, item.file) === file && item.sha256 === source.sha256);
283
303
  if (!retained)
284
304
  throw new AppError("stage_packet_invalid", "Matrix PLAN source lacks retained bytes", 1, { path: source.path });
285
305
  const bytes = Buffer.from(retained.base64, "base64");
@@ -25,6 +25,7 @@ import { prepareVnextMergeRequest } from "./vnext-merge.js";
25
25
  import { validateVnextCodeHandoff } from "./vnext-plan.js";
26
26
  import { assertRunMutationAllowed } from "./run-recovery.js";
27
27
  import { prepareJsonFile, prepareOutputFile } from "../cli/input-preparation.js";
28
+ import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
28
29
  const stage = "code-review";
29
30
  const stageDir = vnextStageDirectory(stage);
30
31
  const baselineAspects = ["goal_traceability", "coding_standards_design_review", "verification_evidence_review"];
@@ -56,14 +57,15 @@ export function prepareVnextCodeReviewStart(context, input) {
56
57
  const cycle = reviewCycle(run);
57
58
  const codeReportSha256 = sha256File(path.join(home, "05-code", "stage-report.json"));
58
59
  const groups = mode.mode === "off" ? [] : reviewGroups(home);
59
- const batch = { works: groups.map((group, index) => ({ key: `code-review-${index + 1}`, task: reviewerTask(home, group), launch_policy: "fresh_agent_required", result_schema: "dd-flow/code-review-result@1", payload: { kind: "code-review", review_cycle: cycle, group, read_only: true } })) };
60
+ const reviewerInputs = mode.mode === "off" ? [] : reviewerSourceInputs(run.workspace_root, groups.flatMap(group => group.aspect_ids));
61
+ const batch = { works: groups.map((group, index) => ({ key: `code-review-${index + 1}`, task: `${reviewerTask(home, group)}\n${reviewerSourceInstruction(run.workspace_root, group.aspect_ids)}`, launch_policy: "fresh_agent_required", result_schema: "dd-flow/code-review-result@1", payload: { kind: "code-review", review_cycle: cycle, group, read_only: true } })) };
60
62
  const items = mode.mode === "off" ? [] : prepareWorkBatch(context, { parentWorkId: rootWork.work_id, value: batch });
61
63
  const template = mode.mode === "off" ? "" : read(path.join(run.workspace_root, ".memory-bank", "dd-flow", "vnext", "code-review.md"));
62
64
  const checks = mode.mode === "off" ? [] : acceptedCodeChecks(context, project.id, run.id).map(({ id, purpose }) => ({ id, purpose }));
63
65
  for (const name of ["review-context.json", ...(mode.mode === "off" ? ["decision.json"] : ["review-work-batch.json", "fanout.json"])])
64
66
  prepareOutputFile(path.join(root, name));
65
67
  const matrix = mode.mode === "off" ? null : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", stageAttempt: attachment.attempt });
66
- return { ...common, resumed: false, mode, cycle, codeReportSha256, groups, batch, items, template, checks, matrix };
68
+ return { ...common, resumed: false, mode, cycle, codeReportSha256, groups, batch, items, template, checks, matrix, reviewerInputs };
67
69
  }
68
70
  export function startVnextCodeReview(context, input, prepared = prepareVnextCodeReviewStart(context, input)) {
69
71
  const { projectRoot, project, run, root, rootWork, existingPrompt } = prepared;
@@ -88,7 +90,7 @@ export function startVnextCodeReview(context, input, prepared = prepareVnextCode
88
90
  freezeFlowRunReviewMode(context, { projectRoot, runId: run.id, review: "code", mode: mode.mode, source: mode.source, reason: mode.reason });
89
91
  const promptPath = path.join(root, "stage-prompt.md");
90
92
  const reviewContextPath = path.join(root, "review-context.json");
91
- fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
93
+ fs.writeFileSync(reviewContextPath, `${JSON.stringify({ schema_id: "dd-flow/code-review-context@1", reviewer_inputs: prepared.reviewerInputs, cycle, mode: mode.mode, mode_source: mode.source, mode_reason: mode.reason, ...(mode.mode !== "off" ? { read_only_input: { parent_work_id: rootWork.work_id, version: 3 } } : {}), code_report_sha256: codeReportSha256, review_batch_sha256: sha256Value(`${JSON.stringify(batch, null, 2)}\n`), aspect_map_checksums: aspectMapChecksums }, null, 2)}\n`);
92
94
  if (mode.mode === "off") {
93
95
  fs.writeFileSync(path.join(root, "decision.json"), `${JSON.stringify(disabledReviewDecision(), null, 2)}\n`);
94
96
  const prompt = `<stage_identity>\n- RUN: ${run.id}\n- stage: code-review\n- mode: off\n</stage_identity>\n\nCODE-REVIEW is disabled by the frozen RUN configuration. Finish with: ${finishCommand(context, run.id, projectRoot, path.join(root, "decision.json"))}\n${stageLifecycleInstruction}\n`;
@@ -352,7 +354,7 @@ export function assertCodeReviewCoverage(groups, reviewers) {
352
354
  throw new AppError("reviewer_coverage_invalid", "CODE-REVIEW requires every expected reviewer group", 2, { missing_groups: missing });
353
355
  }
354
356
  export function reviewerTask(home, group) { return `Read-only independent CODE review for ${group.key}. Assess every assigned aspect exactly once: ${group.aspect_ids.join(", ")}. Read the accepted PLAN, ${path.join(home, "05-code", "stage-report.json")}, and any files needed under the bounded CODE evidence root ${path.join(home, "05-code")}. Verify that universal and exclusive constraints (only, any, all, never, whole) and stated exceptions were not narrowed. For every changed mutation guarded by membership, ownership, authorization or parent lifecycle state, trace the decision to the write boundary: the predicate must remain in the write statement or the guard and write must share one explicit transaction with the needed lock. A separate earlier read is not proof of current authority or lifecycle state; report a material finding when this invariant is broken. Evidence closes a claim only when it exercises the named failure mechanism; a sequential negative test does not prove a concurrency race, and proof limits cannot waive an accepted obligation. Report only material, evidenced defects: a violated obligation or rule, direct evidence, impact, and minimum required outcome. Do not report taste, cosmetics, or untargeted refactoring. Use local finding ids FIND-001, FIND-002, and so on; dd-flow adds the Work-qualified canonical reference. Return dd-flow/code-review-result@1.`; }
355
- function orchestratorPrompt(context, input) { const { template, checks } = input; const decision = path.join(input.root, "decision.json"); return ["<stage_identity>", `- RUN: ${input.run.id}`, `- root Work: ${input.rootWork.work_id}`, `- stage: ${stage}`, `- mode: ${input.mode}`, "</stage_identity>", "", "<trusted_runtime_context>", `- project root: ${input.projectRoot}`, `- immutable write workspace: ${input.run.workspace_root}`, `- stage workspace: ${input.root}`, `- bounded CODE evidence root: ${path.join(input.run.run_root, "05-code")}`, "CODE is already semantically verified and all declared checks passed. Do not redo CODE verification; conduct independent quality review.", "</trusted_runtime_context>", "", "<review_groups>", ...input.groups.map((group) => `- ${group.key}: ${group.aspect_ids.join(", ")}`), "</review_groups>", "", "<execution_commands>", "Do not choose a provider delegation tool from this stage prompt. At the Work-graph boundary, return immediately; the shared controller supplies the selected adapter's native tool, exact parameters, child task, and wait contract. Each reviewer Work must run in one fresh child session. The coordinator must never invoke a Work start_command itself: a child runs its exact start_command after native delegation. Reviewers are read-only and must not create subagents. Do not interrupt a quiet worker.", "Ready reviewer Works: " + runtimeCommand(context, ["work", "ls", "--run", input.run.id, "--ready", "--project-root", input.projectRoot, "--json"]), `Write the decision to this exact absolute path, not a relative look-alike: ${decision}`, `Finish only after every reviewer Work settles and that exact file exists: ${finishCommand(context, input.run.id, input.projectRoot, decision)}`, "Reviewer results use local FIND-NNN ids. Classify them by the canonical WRK-.../FIND-NNN finding_ref returned by dd-flow. Fix P0/P1. Fix bounded safe P2 by default; defer only a legitimate P2 with a named DEF. P3 is an observation or a reasoned rejection, not automatic repair/DEF. For every disposition fix, check_refs is mandatory: choose the one or more causal checks from the accepted list below that the repair must rerun. Do not guess a CHK id or copy every check. The first successful Finish freezes this decision and creates one repair Work when needed. Do not invoke that Work's start_command from the coordinator Session: end this Turn so the controller can launch a fresh child, then call the returned next.finish_command after it completes. Internal invocation IDs are runtime-owned and must not be copied or reused. Review is not repeated.", "If the aggregate gate fails with repair_required, its repair Work is already registered with all causal checks. End this Turn so the controller launches a fresh child. Do not copy receipt IDs, create another repair, invoke its start_command in the coordinator, or edit invisibly in the root orchestrator.", `Accepted repair checks: ${JSON.stringify(checks)}`, "```json", JSON.stringify({ schema_id: "dd-flow/code-review-decision@3", summary: "Evidence-backed conclusion.", findings: [{ finding_ref: "WRK-001-review/FIND-001", disposition: "fix | defer | reject | duplicate", reason: "Why this classification is correct.", check_refs: ["CHK-CAUSAL-CHECK only when disposition is fix"], def_id: "DEF-0001 only for an allowed P2 deferral", duplicate_of: "canonical finding_ref only for duplicate" }] }, null, 2), "```", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n"); }
357
+ function orchestratorPrompt(context, input) { const { template, checks } = input; const decision = path.join(input.root, "decision.json"); return ["<stage_identity>", `- RUN: ${input.run.id}`, `- root Work: ${input.rootWork.work_id}`, `- stage: ${stage}`, `- mode: ${input.mode}`, "</stage_identity>", "", "<trusted_runtime_context>", `- project root: ${input.projectRoot}`, `- immutable write workspace: ${input.run.workspace_root}`, `- stage workspace: ${input.root}`, `- bounded CODE evidence root: ${path.join(input.run.run_root, "05-code")}`, "CODE passed its declared checks; each check proves only its named contour and proof limits, not complete semantic acceptance. Conduct independent quality review and do not manually rerun every declared check.", "</trusted_runtime_context>", "", "<review_groups>", ...input.groups.map((group) => `- ${group.key}: ${group.aspect_ids.join(", ")}`), "</review_groups>", "", "<execution_commands>", "Do not choose a provider delegation tool from this stage prompt. At the Work-graph boundary, return immediately; the shared controller supplies the selected adapter's native tool, exact parameters, child task, and wait contract. Each reviewer Work must run in one fresh child session. The coordinator must never invoke a Work start_command itself: a child runs its exact start_command after native delegation. Reviewers are read-only and must not create subagents. Do not interrupt a quiet worker.", "Ready reviewer Works: " + runtimeCommand(context, ["work", "ls", "--run", input.run.id, "--ready", "--project-root", input.projectRoot, "--json"]), `Write the decision to this exact absolute path, not a relative look-alike: ${decision}`, `Finish only after every reviewer Work settles and that exact file exists: ${finishCommand(context, input.run.id, input.projectRoot, decision)}`, "Reviewer results use local FIND-NNN ids. Classify them by the canonical WRK-.../FIND-NNN finding_ref returned by dd-flow. Fix P0/P1. Fix bounded safe P2 by default; defer only a legitimate P2 with a named DEF. P3 is an observation or a reasoned rejection, not automatic repair/DEF. For every disposition fix, check_refs is mandatory: choose the one or more causal checks from the accepted list below that the repair must rerun. Do not guess a CHK id or copy every check. The first successful Finish freezes this decision and creates one repair Work when needed. Do not invoke that Work's start_command from the coordinator Session: end this Turn so the controller can launch a fresh child, then call the returned next.finish_command after it completes. Internal invocation IDs are runtime-owned and must not be copied or reused. Review is not repeated.", "If the aggregate gate fails with repair_required, its repair Work is already registered with all causal checks. End this Turn so the controller launches a fresh child. Do not copy receipt IDs, create another repair, invoke its start_command in the coordinator, or edit invisibly in the root orchestrator.", `Accepted repair checks: ${JSON.stringify(checks)}`, "```json", JSON.stringify({ schema_id: "dd-flow/code-review-decision@3", summary: "Evidence-backed conclusion.", findings: [{ finding_ref: "WRK-001-review/FIND-001", disposition: "fix | defer | reject | duplicate", reason: "Why this classification is correct.", check_refs: ["CHK-CAUSAL-CHECK only when disposition is fix"], def_id: "DEF-0001 only for an allowed P2 deferral", duplicate_of: "canonical finding_ref only for duplicate" }] }, null, 2), "```", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n"); }
356
358
  function canonicalReviewEvidence(decision, reviewers) {
357
359
  const known = new Set(canonicalCodeFindings(reviewers).map((item) => item.finding_ref));
358
360
  const items = new Map(decision.findings.map((item) => [item.finding_ref, item]));
@@ -23,6 +23,7 @@ import { getStageFanoutCapacity, readFanoutDescriptor, subagentCapacityKey, writ
23
23
  import { stageGraphScope } from "./stage-work-graph.js";
24
24
  import { stageLifecycleInstruction } from "../harness-runtime/lib/delegation-instructions.mjs";
25
25
  import { prepareRunVerificationMatrix, acceptVerificationMatrix, commitVerificationMatrixStart, resumeVerificationMatrix, verificationMatrixInstruction, latestRunVerificationMatrix } from "./verification-matrix.js";
26
+ import { reviewerSourceInputs, reviewerSourceInstruction } from "./review-copy.js";
26
27
  const stage = "plan-review";
27
28
  const stageDir = vnextStageDirectory(stage);
28
29
  const reviewTask = "Independently review the accepted PLAN and decide whether CODE may open.";
@@ -55,6 +56,10 @@ export function prepareVnextPlanReviewStart(context, input) {
55
56
  assertReviewWorkspace(run.workspace_root, protocolIds);
56
57
  const groups = reviewGroups(home, run.workspace_root);
57
58
  const effective = resolveMode(requested, groups, home, run.workspace_root);
59
+ const reviewerInputs = effective === "off" ? [] : reviewerSourceInputs(run.workspace_root, groups.flatMap(group => group.aspect_ids));
60
+ if (effective !== "off")
61
+ for (const group of groups)
62
+ group.task += `\n${reviewerSourceInstruction(run.workspace_root, group.aspect_ids)}`;
58
63
  const existing = context.db.get("SELECT work_id, status FROM works WHERE project_id = ? AND run_id = ? AND task = ? ORDER BY created_at DESC LIMIT 1", [project.id, run.id, reviewTask]);
59
64
  const batchChecksum = checksum(batch), planChecksum = planSetChecksum(home, run.workspace_root);
60
65
  if (input.externalContext)
@@ -62,7 +67,7 @@ export function prepareVnextPlanReviewStart(context, input) {
62
67
  prepareOutputFile(path.join(root, "stage-prompt.md"));
63
68
  const attachment = existing?.status === "running" ? null : prepareFlowRunStageAttachment(context, { projectRoot, runId: run.id, stage, dir: stageDir, status: "running", dataSchemaId: "dd-flow/plan-review-decision@3" });
64
69
  const matrix = existing?.status === "running" ? latestRunVerificationMatrix(context, project.id, run.id, stage, "input") : prepareRunVerificationMatrix(context, { projectId: project.id, runId: run.id, stage, role: "input", ...(attachment ? { stageAttempt: attachment.attempt } : {}) });
65
- const active = { ...common, requested, protocolIds, groups, effective, workspaceRoute, batchChecksum, planChecksum, matrix };
70
+ const active = { ...common, requested, protocolIds, groups, effective, workspaceRoute, batchChecksum, planChecksum, matrix, reviewerInputs };
66
71
  if (existing?.status === "running") {
67
72
  const orchestration = readFanoutDescriptor(root);
68
73
  if (!orchestration)
@@ -163,7 +168,7 @@ export function startVnextPlanReview(context, input, prepared = prepareVnextPlan
163
168
  const workSessionId = String(binding.work_session_id);
164
169
  const sessionId = String(binding.session_id);
165
170
  const orchestration = writeFanoutDescriptor(root, { stage, parent_work_id: workId, dispatch: "plan-review", capacity_required: true, scope: stageGraphScope(context, project.id, run.id, stage) });
166
- writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
171
+ writeJson(path.join(root, "work-context.json"), { schema_id: "dd-flow/plan-review-context@1", reviewer_inputs: prepared.reviewerInputs, read_only_input: { parent_work_id: workId, version: 3 }, system: { run_id: run.id, work_id: workId, stage, requested_mode: requested, effective_mode: effective, plan_revision: prepared.revision, plan_checksum: planChecksum, batch_checksum: batchChecksum }, groups, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root, plan_root: planRoot } });
167
172
  refreshRunWorkProjection(context, project.id, run.id);
168
173
  appendFlowRunTimelineEvent(context, project.id, run.id, { type: "work_session_started", stage, work_id: workId, id: workSessionId, session_id: sessionId });
169
174
  return { ok: true, run_id: run.id, stage, outcome: "review_required", work_id: workId, id: workSessionId, prompt_path: promptPath, worker_prompt_markdown: fs.readFileSync(promptPath, "utf8"), ...(externalContext ? { external_context: externalContext } : {}), orchestration, plan_review: { requested_mode: requested, effective_mode: effective, policy_source: policySource(run), groups }, next: { dispatch_command: dispatchCommand(context, run.id, projectRoot), finish_command: finishCommand(context, run.id, projectRoot, path.join(root, "decision.json")) } };
@@ -995,6 +995,8 @@ function assertSharedReviewInput(context, work, run, generation = recoveryGuard(
995
995
  add("run", matrix.metadata.json.path, matrix.metadata.json.sha256);
996
996
  add("run", matrix.metadata.markdown.path, matrix.metadata.markdown.sha256);
997
997
  }
998
+ for (const item of value.reviewer_inputs ?? [])
999
+ add("workspace", item.path, item.sha256);
998
1000
  const runInputs = [...expected].map(([key, sha]) => { const [root, ...parts] = key.split(":"); return { root: root, path: parts.join(":"), sha256: sha }; }).sort((a, b) => `${a.root}:${a.path}`.localeCompare(`${b.root}:${b.path}`));
999
1001
  for (const item of runInputs) {
1000
1002
  const target = reviewInputPath(item.root === "run" ? home : run.workspace_root, item.path);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.122",
3
+ "version": "0.9.0-beta.123",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.23.0",