@deksden-com/dd-flow-cli 0.9.0-beta.8 → 0.9.0-beta.9
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,12 @@
|
|
|
1
1
|
# @deksden-com/dd-flow-cli
|
|
2
2
|
|
|
3
|
+
## 0.9.0-beta.9
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f156ad8: Use harness-qualified native child capacity for PLAN-REVIEW, CODE and
|
|
8
|
+
CODE-REVIEW fan-out instead of creating flow-owned capacity probes.
|
|
9
|
+
|
|
3
10
|
## 0.9.0-beta.8
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
package/dist/build-info.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"cli_package": "@deksden-com/dd-flow-cli",
|
|
3
|
-
"cli_version": "0.9.0-beta.
|
|
4
|
-
"cli_commit": "
|
|
5
|
-
"built_at": "2026-09-
|
|
3
|
+
"cli_version": "0.9.0-beta.9",
|
|
4
|
+
"cli_commit": "7c6e37d05c8039349dee26fb7446abd6dcaef136",
|
|
5
|
+
"built_at": "2026-09-04T15:11:45.164Z",
|
|
6
6
|
"built_with_canon": {
|
|
7
7
|
"version": "4.0.1",
|
|
8
|
-
"commit": "
|
|
8
|
+
"commit": "71c62fccbfa32c61ae77874ff42c8f9fb19e9f81",
|
|
9
9
|
"flow_contract": "dd-flow-canonical-2026-08",
|
|
10
10
|
"repo_root": "/Users/deksden/Documents/_Projects/dd-memorybank",
|
|
11
11
|
"memorybank_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank",
|
|
@@ -7,12 +7,6 @@ import { getFlowRunVariables } from "./runs.js";
|
|
|
7
7
|
import { listWorks } from "./work-registry.js";
|
|
8
8
|
import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
9
9
|
export const subagentCapacityKey = "runtime.subagent.available_slots";
|
|
10
|
-
export const capacityProbe = {
|
|
11
|
-
fanout_size: 15,
|
|
12
|
-
probe_hold_seconds: 60,
|
|
13
|
-
cleanup_deadline_seconds: 180,
|
|
14
|
-
completion_token: "AGENT-NN"
|
|
15
|
-
};
|
|
16
10
|
export function fanoutState(input) {
|
|
17
11
|
if (!input.hasWork)
|
|
18
12
|
return input.dispatch === "none" ? "coordinator_required" : "dispatch_required";
|
|
@@ -70,8 +64,7 @@ export function getVnextFanoutStatus(context, input) {
|
|
|
70
64
|
state: fanoutState({ dispatch: descriptor.dispatch, hasWork, capacityRequired: descriptor.capacity_required, capacityKnown, created: counts.created ?? 0, running: counts.running ?? 0 }),
|
|
71
65
|
capacity: {
|
|
72
66
|
run_key: subagentCapacityKey,
|
|
73
|
-
available_slots: capacityKnown ? available : null
|
|
74
|
-
...(descriptor.capacity_required && !capacityKnown ? { probe: capacityProbe } : {})
|
|
67
|
+
available_slots: capacityKnown ? available : null
|
|
75
68
|
},
|
|
76
69
|
works: { ...counts, ready: ready.works.map((work) => ({ work_id: work.work_id, task: work.task, start_command: work.start_command })) }
|
|
77
70
|
}
|
|
@@ -16,13 +16,10 @@ import { vnextStageDirectory } from "../domain/stage-catalog.js";
|
|
|
16
16
|
import { writeStageReport } from "./stage-report-renderer.js";
|
|
17
17
|
import { assertPortableArtifactRef } from "./portable-refs.js";
|
|
18
18
|
import { applyExternalStageContext } from "./stage-context.js";
|
|
19
|
-
import {
|
|
19
|
+
import { readFanoutDescriptor, subagentCapacityKey, writeFanoutDescriptor } from "./vnext-fanout.js";
|
|
20
20
|
const stage = "plan-review";
|
|
21
21
|
const stageDir = vnextStageDirectory(stage);
|
|
22
22
|
const reviewTask = "Independently review the accepted PLAN and decide whether CODE may open.";
|
|
23
|
-
const capacityProbeFanoutSize = capacityProbe.fanout_size;
|
|
24
|
-
const capacityProbeHoldSeconds = capacityProbe.probe_hold_seconds;
|
|
25
|
-
const capacityProbeDeadlineSeconds = capacityProbe.cleanup_deadline_seconds;
|
|
26
23
|
export function isVnextPlanReviewRun(context, input) {
|
|
27
24
|
const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
|
|
28
25
|
return Boolean(context.db.get("SELECT id FROM runs WHERE project_id = ? AND id = ? AND flow_kind = 'vnext_protocolize'", [project.id, input.runId]));
|
|
@@ -126,7 +123,7 @@ export function dispatchVnextPlanReview(context, input) {
|
|
|
126
123
|
if (capacity.pending)
|
|
127
124
|
return capacity.response;
|
|
128
125
|
if (capacity.availableSlots < 1)
|
|
129
|
-
throw new AppError("
|
|
126
|
+
throw new AppError("subagent_capacity_unqualified", "PLAN-REVIEW requires one qualified native subagent slot", 1, { run_id: run.id });
|
|
130
127
|
const existing = context.db.all("SELECT work_id, task, status, result, created_at, launch_policy FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id = ?", [project.id, run.id, parent.work_id]);
|
|
131
128
|
const latest = (group) => existing.find((item) => item.task === group.task);
|
|
132
129
|
const pending = groups.filter((group) => !latest(group));
|
|
@@ -162,14 +159,14 @@ export function recordVnextPlanReviewCapacity(context, input) {
|
|
|
162
159
|
const prior = variables.variables[reviewCapacityKey];
|
|
163
160
|
if (typeof prior === "number") {
|
|
164
161
|
if (prior === input.availableSlots)
|
|
165
|
-
return { ok: true, run_id: run.id, capacity: { available_slots: prior, source: "
|
|
162
|
+
return { ok: true, run_id: run.id, capacity: { available_slots: prior, source: "harness_qualification", reused: true } };
|
|
166
163
|
setRuntimeRunVariable(context, { projectRoot, runId: run.id, key: reviewCapacityKey, value: input.availableSlots });
|
|
167
|
-
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "subagent_capacity_refreshed", previous_available_slots: prior, available_slots: input.availableSlots,
|
|
168
|
-
return { ok: true, run_id: run.id, capacity: { available_slots: input.availableSlots, previous_available_slots: prior, source: "
|
|
164
|
+
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "subagent_capacity_refreshed", previous_available_slots: prior, available_slots: input.availableSlots, source: "harness_qualification" });
|
|
165
|
+
return { ok: true, run_id: run.id, capacity: { available_slots: input.availableSlots, previous_available_slots: prior, source: "harness_qualification", refreshed: true } };
|
|
169
166
|
}
|
|
170
167
|
setRuntimeRunVariable(context, { projectRoot, runId: run.id, key: reviewCapacityKey, value: input.availableSlots });
|
|
171
|
-
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "subagent_capacity_observed", available_slots: input.availableSlots,
|
|
172
|
-
return { ok: true, run_id: run.id, capacity: { available_slots: input.availableSlots, source: "
|
|
168
|
+
appendFlowRunTimelineEvent(context, project.id, run.id, { type: "subagent_capacity_observed", available_slots: input.availableSlots, source: "harness_qualification" });
|
|
169
|
+
return { ok: true, run_id: run.id, capacity: { available_slots: input.availableSlots, source: "harness_qualification" } };
|
|
173
170
|
}
|
|
174
171
|
export function finishVnextPlanReview(context, input) {
|
|
175
172
|
const projectRoot = resolveProjectRoot(input.projectRoot);
|
|
@@ -184,7 +181,7 @@ export function finishVnextPlanReview(context, input) {
|
|
|
184
181
|
throw new AppError("invalid_work_state", "PLAN-REVIEW has no running parent Work", 2);
|
|
185
182
|
const decisionPath = path.resolve(input.decisionFile ?? path.join(root, "decision.json"));
|
|
186
183
|
const decision = readDecision(context, projectRoot, run.id, home, decisionPath);
|
|
187
|
-
const children = context.db.all("SELECT work_id, task, status, result, created_at FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id = ?
|
|
184
|
+
const children = context.db.all("SELECT work_id, task, status, result, created_at FROM works WHERE project_id = ? AND run_id = ? AND parent_work_id = ?", [project.id, run.id, parent.work_id]);
|
|
188
185
|
const contextFile = readJson(path.join(root, "work-context.json"));
|
|
189
186
|
const groups = contextFile.groups ?? [];
|
|
190
187
|
const reviewedPlanRevision = contextFile.system?.plan_revision;
|
|
@@ -267,7 +264,7 @@ function orchestratorPrompt(context, input) {
|
|
|
267
264
|
const decision = path.join(input.root, "decision.json");
|
|
268
265
|
const revision = currentPlanRevision(input.home, input.run.workspace_root);
|
|
269
266
|
const workspaceContract = ["<workspace_contract>", `- route: ${input.workspaceRoute.route}`, `- feature branch: ${input.workspaceRoute.feature_branch ?? "not applicable"}`, `- base commit: ${input.workspaceRoute.base_ref ?? "not applicable"}`, `- read/write workspace: ${input.run.workspace_root}`, "The CLI verified this frozen route. All plan and correction writes belong in the named workspace; project root remains only the stable lifecycle identity. Do not create, switch, merge or delete branches/worktrees.", "</workspace_contract>"].join("\n");
|
|
270
|
-
return ["<stage_identity>", `- RUN: ${input.run.id}`, `- Work: ${input.workId}`, "- Stage: plan-review", `- Mode: ${input.effective}`, "</stage_identity>", "", "<trusted_runtime_context>", "These facts were collected by dd-flow. Trust them; do not repeat CLI, Git, compatibility, permission or schema discovery.", `- Project root: ${input.projectRoot}`, `- Stage workspace: ${input.root}`, `- PLAN revision: ${revision}`, `- PLAN report checksum: ${input.planChecksum}`, `- Generated CODE batch checksum: ${input.batchChecksum}`, "</trusted_runtime_context>", "", workspaceContract, "", "<review_groups>", ...input.groups.map((group) => `- ${group.key}: ${group.aspect_ids.join(", ")}`), "</review_groups>", "", "<execution_commands>", `Dispatch fresh reviewers: ${dispatchCommand(context, input.run.id, input.projectRoot)}`, `If dispatch
|
|
267
|
+
return ["<stage_identity>", `- RUN: ${input.run.id}`, `- Work: ${input.workId}`, "- Stage: plan-review", `- Mode: ${input.effective}`, "</stage_identity>", "", "<trusted_runtime_context>", "These facts were collected by dd-flow. Trust them; do not repeat CLI, Git, compatibility, permission or schema discovery.", `- Project root: ${input.projectRoot}`, `- Stage workspace: ${input.root}`, `- PLAN revision: ${revision}`, `- PLAN report checksum: ${input.planChecksum}`, `- Generated CODE batch checksum: ${input.batchChecksum}`, "</trusted_runtime_context>", "", workspaceContract, "", "<review_groups>", ...input.groups.map((group) => `- ${group.key}: ${group.aspect_ids.join(", ")}`), "</review_groups>", "", "<execution_commands>", `Dispatch fresh reviewers: ${dispatchCommand(context, input.run.id, input.projectRoot)}`, `If dispatch reports qualified_capacity_required, stop. The external harness controller qualifies the selected profile outside this RUN and records the resulting integer with ${capacityRecordCommand(context, input.run.id, input.projectRoot, "<qualified-native-child-count>")}. PLAN-REVIEW never launches a capacity probe.`, "After dispatch, launch at most the qualified capacity at once. If more independent reviewer Works remain, wait for the current wave to settle, then start the unchanged queued Works in the next wave. A reviewer launch rejected before it starts is not review evidence: do not create a replacement; wait for a running wave to settle and start that same queued Work. Each reviewer must be a genuinely fresh harness child Session. The lifecycle adapter binds that observed Session; do not bind or supply a Session ID manually. Reviewers are read-only and must not create children. As soon as a reviewer result is accepted, release that reviewer Session when the harness permits; do not let finished disposable workers occupy slots before the next wave.", "Review the execution environment of every selected check as part of its proof: a reset/fixture process, service process and client process must share the intended data and configuration world. A runtime entrypoint that can break that invariant must be explicit in one Work's task and verification and ordered before its consumer. planned_write_areas may advertise likely overlap, but do not treat them as ownership; required_read alone is not a delivery plan.", "If the final decision needs user input with no reasonable default, 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, stop, then resume this same PLAN-REVIEW Work. Do not write decision.json or finish first.", `When all reviewer results are complete and every user question is resolved, classify every material finding, fix accepted findings in this same PLAN-REVIEW Work, then write ${decision} and finish: ${finishCommand(context, input.run.id, input.projectRoot, decision)}`, "Reviewer findings use local FIND-NNN ids. dd-flow exposes each finding to this coordinator as WRK-.../FIND-NNN; use that canonical finding_ref in the decision.", "A completed reviewer result with needs_changes or blocked is evidence, not the stage outcome. Classify its material findings and apply accepted fixes in this one review pass; do not start a second review automatically. Only a missing, malformed or unfinished reviewer result blocks the stage. For an accepted correction, increment PLAN revision and update only plan.json and the relevant aspect map. Do not edit or list code-work-batch.json: the CLI validates final PLAN and regenerates it. If no material correction is needed, set correction.status=not_required. The CLI checks mechanical handoff coherence; it does not prove semantic correctness.", "```json", JSON.stringify({ schema_id: "dd-flow/plan-review-decision@3", outcome: "accepted | failed | cancelled", summary: "Concise evidence-backed final decision.", finding_decisions: [{ finding_ref: "WRK-001-review/FIND-001", decision: "accepted_fix | rejected | deferred_as_DEF | requires_user | duplicate", reason: "Why." }], correction: { status: "not_required | applied", previous_plan_revision: revision, changed_paths: [], summary: "No material correction was needed, or summarize the applied correction." } }, null, 2), "```", "</execution_commands>", "", "<stage_instructions>", template, "</stage_instructions>", ""].join("\n");
|
|
271
268
|
}
|
|
272
269
|
function reviewGroups(home, workspaceRoot) {
|
|
273
270
|
const root = path.join(home, "03-plan");
|
|
@@ -327,15 +324,15 @@ function plannedReviewWaves(groups, capacity) {
|
|
|
327
324
|
}
|
|
328
325
|
return waves;
|
|
329
326
|
}
|
|
330
|
-
export function
|
|
331
|
-
return { ok: true, run_id: input.runId, stage, outcome: "
|
|
327
|
+
export function capacityQualificationResponse(context, input) {
|
|
328
|
+
return { ok: true, run_id: input.runId, stage, outcome: "qualified_capacity_required", next_action: "qualify_harness_profile_then_record_capacity", next: { record_command: capacityRecordCommand(context, input.runId, input.projectRoot, "<qualified-native-child-count>"), retry_command: dispatchCommand(context, input.runId, input.projectRoot) } };
|
|
332
329
|
}
|
|
333
330
|
function ensureReviewCapacity(context, input) {
|
|
334
331
|
const variables = getFlowRunVariables(context, { projectRoot: input.projectRoot, runId: input.runId });
|
|
335
332
|
const known = variables.variables[reviewCapacityKey];
|
|
336
333
|
if (typeof known === "number" && Number.isInteger(known) && known >= 0)
|
|
337
334
|
return { pending: false, availableSlots: known };
|
|
338
|
-
return { pending: true, response:
|
|
335
|
+
return { pending: true, response: capacityQualificationResponse(context, { projectRoot: input.projectRoot, runId: input.runId }) };
|
|
339
336
|
}
|
|
340
337
|
function protocolIdsForReview(home) {
|
|
341
338
|
const report = readJson(path.join(home, "02-protocolize", "stage-report.json"));
|
|
@@ -454,14 +451,14 @@ function finishTerminalReview(context, input) {
|
|
|
454
451
|
const now = context.now();
|
|
455
452
|
const childStatus = input.decision.outcome === "cancelled" ? "cancelled" : "failed";
|
|
456
453
|
// A terminal decision is structured cancellation of the review subtree. It
|
|
457
|
-
// must settle
|
|
458
|
-
//
|
|
454
|
+
// must settle semantic reviewers before its parent Work can settle; otherwise
|
|
455
|
+
// the RUN contains abandoned active Work/Session links.
|
|
459
456
|
settleReviewChildren(context, { projectId: input.projectId, runId: input.run.id, parentWorkId: input.parentWorkId, status: childStatus, reason: `PLAN-REVIEW terminal decision: ${input.decision.summary}`, now });
|
|
460
457
|
settleReviewParent(context, { projectRoot: input.projectRoot, projectId: input.projectId, runId: input.run.id, parentWorkId: input.parentWorkId, status: childStatus, result: input.decision.summary, reason: `plan_review_${input.decision.outcome}`, resultPath: path.join(input.root, "stage-report.json") });
|
|
461
458
|
const rootWork = rootWorkId(context, input.projectId, input.run.id);
|
|
462
459
|
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]);
|
|
463
460
|
closeRunningWorkSession(context, rootWork, childStatus, now);
|
|
464
|
-
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 = ?
|
|
461
|
+
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]);
|
|
465
462
|
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) });
|
|
466
463
|
writeReport(input.root, report);
|
|
467
464
|
refreshRunWorkProjection(context, input.projectId, input.run.id);
|
|
@@ -81,9 +81,9 @@ export function startVnextPlan(context, input) {
|
|
|
81
81
|
const measuredCapacity = runVariables.variables[subagentCapacityKey];
|
|
82
82
|
const mergeRequired = runEndsAtMerge(context, projectRoot, run.id);
|
|
83
83
|
const capacityContext = typeof measuredCapacity === "number" && Number.isInteger(measuredCapacity) && measuredCapacity >= 0
|
|
84
|
-
? `- The
|
|
85
|
-
: "- Reviewer capacity is not
|
|
86
|
-
const reviewGroupingRule = "Group only semantically compatible applicable aspects, preserving real trust, irreversible, high-risk and hard-dependency boundaries. Prefer the fewest groups that retain independent review value, normally one review wave. Put two or three compatible aspects in a group; do not create one group per aspect merely for convenience. A later PLAN-REVIEW dispatch
|
|
84
|
+
? `- 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.`
|
|
85
|
+
: "- Reviewer capacity is not qualified yet. PLAN must not qualify or launch reviewers; an external harness controller supplies it before fan-out.";
|
|
86
|
+
const reviewGroupingRule = "Group only semantically compatible applicable aspects, preserving real trust, irreversible, high-risk and hard-dependency boundaries. Prefer the fewest groups that retain independent review value, normally one review wave. Put two or three compatible aspects in a group; do not create one group per aspect merely for convenience. A later PLAN-REVIEW dispatch uses externally qualified capacity to schedule these semantic groups into waves; do not invent a capacity value here.";
|
|
87
87
|
const checkProfile = path.join(run.workspace_root, ".memory-bank", "spec", "engineering", "code-check-profile.json");
|
|
88
88
|
const policyMergeAliases = codeCheckProfile?.mandatory_by_gate.merge ?? [];
|
|
89
89
|
const mergeContract = mergeRequired
|