@deksden-com/dd-flow-cli 0.9.0-beta.109 → 0.9.0-beta.110

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.110
4
+
5
+ ### Patch Changes
6
+
7
+ - Pass retained runtime ownership to the ZCode ACP provider so managed Turns do not accept an inferred idle result as native completion. Reject an empty review set while PLAN-REVIEW is explicitly standard/deep, before publishing PLAN, and explain that mode in the shared PLAN prompt.
8
+
3
9
  ## 0.9.0-beta.109
4
10
 
5
11
  ### Patch Changes
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.109",
4
- "cli_commit": "350120c67365b0b0c8f2fc81c7cd73548aae6617",
5
- "built_at": "2026-09-27T07:05:10.542Z",
3
+ "cli_version": "0.9.0-beta.110",
4
+ "cli_commit": "dbb41705604e422795167f32b74fb92ee34fa1ae",
5
+ "built_at": "2026-09-27T09:03:36.141Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
@@ -593,11 +593,16 @@ export class DaemonRuntime {
593
593
  }
594
594
  }
595
595
 
596
+ export function zcodeBridgeEnv(config, daemonId) {
597
+ return { ...config.env, DD_FLOW_DAEMON_ID: daemonId,
598
+ DD_FLOW_RUNTIME_OWNER: config.runtime_owner ? JSON.stringify(config.runtime_owner) : undefined };
599
+ }
600
+
596
601
  export async function serveDaemon(stateDir) {
597
602
  const paths = locations(stateDir);
598
603
  const state = await readState(paths.dir);
599
604
  if (!state || state.schema_id !== STATE_SCHEMA) throw new DaemonError("daemon_state_missing", "daemon state is missing or incompatible");
600
- const bridge = new AcpBridge({ ...state.config, env: { ...state.config.env, DD_FLOW_DAEMON_ID: state.daemon_id } });
605
+ const bridge = new AcpBridge({ ...state.config, env: zcodeBridgeEnv(state.config, state.daemon_id) });
601
606
  const initialized = await startManagedBridge({ config: state.config, state, bridge, kind: "zcode-provider", publish: snapshot => writeState(paths.state, snapshot) });
602
607
  const runtime = new DaemonRuntime(paths, state, bridge, initialized);
603
608
  scheduleDaemonProcessHeartbeat(state.config, state.resource_process);
@@ -803,6 +803,7 @@ export function zcodeLifecycleQualification(sha256, bridgeCommit, harnessContrac
803
803
  ["8c0a893f3c26a0b96cb138acf762db84c800298b", ZCODE_HARNESS_CONTRACT],
804
804
  ["f4d47b517073f9bdb8d4eba3d0d59ce66c6a2833", "dd-zcode-harness@2"],
805
805
  ["5a8c89ab716a4b2ccc50df81cb4d204ff5c06368", "dd-zcode-harness@2"],
806
+ ["91a1470650f5fd6105f9863fd31d1cad7b90a109", "dd-zcode-harness@2"],
806
807
  ]);
807
808
  const qualified = qualifiedBridgeCommits.get(bridgeCommit) === harnessContract;
808
809
  return { status: qualified ? "qualified" : "unqualified", mechanism: "cli-invocation@1", sha256,
@@ -91,6 +91,7 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
91
91
  `${flowCommand(context)} schema validate --schema plan-aspect-map --file ${JSON.stringify(mapPaths[index])} --project-root ${JSON.stringify(run.workspace_root)} --run ${run.id} --json`
92
92
  ]);
93
93
  const measuredCapacity = runVariables.variables[subagentCapacityKey];
94
+ const planReviewMode = requestedPlanReviewMode(context, project.id, run.id);
94
95
  const capacityContext = typeof measuredCapacity === "number" && Number.isInteger(measuredCapacity) && measuredCapacity >= 0
95
96
  ? `- The qualified reviewer capacity is ${measuredCapacity}. This is a runtime fact for later PLAN-REVIEW dispatch; do not repeat qualification or invent a different value.`
96
97
  : "- Reviewer capacity is not qualified yet. PLAN must not qualify or launch reviewers; an external harness controller supplies it before fan-out.";
@@ -107,7 +108,10 @@ export function startVnextPlan(context, input, prepared = prepareVnextPlanStart(
107
108
  // This explicit final rule supersedes historical pack wording: a flow gate
108
109
  // has only executable autonomous evidence. Human or external confirmation
109
110
  // is neither a check nor a permitted completion condition.
110
- fs.writeFileSync(promptPath, `${prompt}\n<verification_rule>Every flow check must be an autonomous executable command. Do not declare external/manual proof or a human review as a check, evidence substitute, DEF, or gate. The only user interaction supported by this flow is an explicit stage pause for a material unanswered question.</verification_rule>\n${stageLifecycleInstruction}\n`);
111
+ const reviewModeRule = planReviewMode === "standard" || planReviewMode === "deep"
112
+ ? `PLAN-REVIEW mode is ${planReviewMode}: assign at least one genuinely applicable aspect to a review group. Small feature size alone does not make every aspect not applicable; PLAN finish rejects an empty review set.`
113
+ : `PLAN-REVIEW mode is ${planReviewMode}: include review groups only for genuinely applicable aspects; an empty set is allowed.`;
114
+ fs.writeFileSync(promptPath, `${prompt}\n<review_mode_rule>${reviewModeRule}</review_mode_rule>\n<verification_rule>Every flow check must be an autonomous executable command. Do not declare external/manual proof or a human review as a check, evidence substitute, DEF, or gate. The only user interaction supported by this flow is an explicit stage pause for a material unanswered question.</verification_rule>\n${stageLifecycleInstruction}\n`);
111
115
  const externalContext = applyExternalStageContext({ stageRoot: root, promptPath, ...(input.externalContext ? { loaded: input.externalContext } : {}) });
112
116
  fs.writeFileSync(path.join(root, "work-context.json"), JSON.stringify({ schema_id: "dd-flow/work-context@1", system: { run_id: run.id, work_id: planWorkId, stage: "plan" }, workspace: { project_root: projectRoot, workspace_root: run.workspace_root, stage_root: root }, artifacts: artifactMaterialization, input: { protocols, owned_obligations: Object.fromEntries(owned) } }, null, 2));
113
117
  const binding = bindStageCoordinatorWork(context, { workId: planWorkId, hookEventId: input.hookEventId, stage: "plan", promptPath, resultPath: path.join(root, "stage-report.json"), ...(input.contextSha256 ? { contextSha256: input.contextSha256 } : {}) });
@@ -193,6 +197,7 @@ export function prepareVnextPlanArtifacts(context, input) {
193
197
  const workspaceRoot = input.workspaceRoot ?? input.projectRoot;
194
198
  const planFiles = input.protocols.map((id) => path.join(workspaceRoot, ".memory-bank", "protocol", id, "plan.json"));
195
199
  const mapFiles = input.protocols.map((id) => path.join(input.home, "03-plan", id, "aspect-map.json"));
200
+ let reviewGroupCount = 0;
196
201
  const batch = path.join(input.home, "03-plan", "code-work-batch.json");
197
202
  const ownership = protocolOwnership(input.home, input.protocols);
198
203
  const obligations = acceptedObligations(input.home);
@@ -232,12 +237,19 @@ export function prepareVnextPlanArtifacts(context, input) {
232
237
  throw new AppError("validation", "Accepted aspect-map requires normalization; regenerate it in PLAN", 2, { file });
233
238
  const value = JSON.parse(bytes);
234
239
  validateAspectMap(file, input.protocols, workspaceRoot, value);
240
+ reviewGroupCount += Array.isArray(value.review_groups) ? value.review_groups.length : 0;
235
241
  publications.set(file, bytes);
236
242
  }
237
243
  catch (error) {
238
244
  failures.push(validationFailure(file, error));
239
245
  }
240
246
  }
247
+ if (!failures.length && reviewGroupCount === 0) {
248
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
249
+ const mode = requestedPlanReviewMode(context, project.id, input.runId);
250
+ if (mode === "standard" || mode === "deep")
251
+ failures.push(validationFailure(mapFiles[0] ?? batch, new AppError("validation", "Configured PLAN-REVIEW requires at least one applicable aspect and review group in the PLAN map", 2, { mode })));
252
+ }
241
253
  if (!failures.length) {
242
254
  try {
243
255
  validatePsetCheckIdentity(plans);
@@ -670,6 +682,10 @@ function validatePsetCheckIdentity(plans) {
670
682
  owners.set(check.id, plan.protocolId);
671
683
  }
672
684
  }
685
+ function requestedPlanReviewMode(context, projectId, runId) {
686
+ const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]);
687
+ return JSON.parse(row?.index_json ?? "{}").settings?.plan_review?.mode ?? "auto";
688
+ }
673
689
  function runEndsAtMerge(context, projectRoot, runId) {
674
690
  const project = requireProjectByRoot(context, resolveProjectRoot(projectRoot));
675
691
  const row = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.109",
3
+ "version": "0.9.0-beta.110",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {