@deksden-com/dd-flow-cli 0.9.0-beta.87 → 0.9.0-beta.89
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 +14 -0
- package/dist/build-info.json +4 -4
- package/dist/cli/command-inputs.js +13 -10
- package/dist/cli/help.js +4 -0
- package/dist/cli/hook-ingress.js +2 -2
- package/dist/cli/run-cli.js +48 -15
- package/dist/services/codex-hook-delivery.js +3 -3
- package/dist/services/command-context.js +80 -0
- package/dist/services/continuation-outcome.js +20 -0
- package/dist/services/controller-fanout.js +4 -2
- package/dist/services/hooks.js +21 -15
- package/dist/services/lifecycle-command.js +27 -0
- package/dist/services/lifecycle-invocations.js +119 -38
- package/dist/services/recovery-snapshot-database.js +1 -1
- package/dist/services/repair-intents.js +40 -0
- package/dist/services/run-controller.js +12 -3
- package/dist/services/runtime-command.js +43 -0
- package/dist/services/stage-pause.js +7 -21
- package/dist/services/stage-work-graph.js +35 -0
- package/dist/services/vnext-code-review.js +13 -11
- package/dist/services/vnext-code.js +39 -21
- package/dist/services/vnext-fanout.js +27 -6
- package/dist/services/vnext-merge.js +13 -10
- package/dist/services/vnext-plan-review.js +6 -5
- package/dist/services/work-registry.js +18 -9
- package/dist/storage/database.js +15 -0
- package/package.json +1 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { managedLifecycleCommand } from "./lifecycle-invocations.js";
|
|
2
|
+
import { bindStageWork, currentStageWorkIds, stageWorkIds } from "./stage-work-graph.js";
|
|
3
|
+
import { runtimeCommand } from "./runtime-command.js";
|
|
2
4
|
import crypto from "node:crypto";
|
|
3
5
|
import fs from "node:fs";
|
|
4
6
|
import path from "node:path";
|
|
5
|
-
import { quote } from "shell-quote";
|
|
6
7
|
import { AppError } from "../shared/errors.js";
|
|
7
8
|
import { entityReferenceVariants, shortEntityReference } from "../shared/entity-references.js";
|
|
8
9
|
import { resolveWorkReference } from "../storage/work-references.js";
|
|
@@ -104,6 +105,8 @@ export function createChildWork(context, input) {
|
|
|
104
105
|
const id = nextWorkId(context, parent.project_id, input.slug);
|
|
105
106
|
const now = context.now();
|
|
106
107
|
context.db.run(`INSERT INTO works (${workColumns}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, '[]', 'created', NULL, ?, NULL, ?, NULL)`, [id, parent.project_id, parent.run_id, parent.work_id, input.task, input.launchPolicy, input.resultSchema ?? null, input.payload ? JSON.stringify(input.payload) : null, now, now]);
|
|
108
|
+
if (input.stage)
|
|
109
|
+
bindStageWork(context, { projectId: parent.project_id, runId: parent.run_id, workId: id, stage: input.stage });
|
|
107
110
|
refreshRunWorkProjection(context, parent.project_id, parent.run_id);
|
|
108
111
|
return requireWork(context, id);
|
|
109
112
|
});
|
|
@@ -181,8 +184,14 @@ export function addWorkBatch(context, input) {
|
|
|
181
184
|
for (const item of proposed)
|
|
182
185
|
context.db.run(`INSERT INTO works (work_id, project_id, run_id, parent_work_id, task, launch_policy, result_schema, payload_json, depends_on_json, status, result, started_at, created_at, updated_at, completed_at)
|
|
183
186
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'created', NULL, NULL, ?, ?, NULL)`, [item.id, parent.project_id, parent.run_id, item.parentId, item.task, item.launch_policy ?? "reuse_allowed", item.result_schema ?? null, item.payload ? JSON.stringify(item.payload) : null, JSON.stringify(item.dependencies), now, now]);
|
|
184
|
-
|
|
187
|
+
const run = context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [parent.project_id, parent.run_id]);
|
|
188
|
+
const stages = run ? (JSON.parse(run.index_json).stage_runs ?? []) : [];
|
|
189
|
+
const ownerStage = input.stage ?? [...stages].reverse().find(stage => stage.status === "running")?.stage;
|
|
190
|
+
for (const item of proposed) {
|
|
191
|
+
if (ownerStage)
|
|
192
|
+
bindStageWork(context, { projectId: parent.project_id, runId: parent.run_id, workId: item.id, stage: ownerStage, ...(input.cycle ? { cycle: input.cycle } : {}) });
|
|
185
193
|
appendFlowRunTimelineEvent(context, parent.project_id, parent.run_id, { type: "work_materialized", work_id: item.id, parent_work_id: item.parentId, depends_on: item.dependencies, launch_policy: item.launch_policy ?? "reuse_allowed" });
|
|
194
|
+
}
|
|
186
195
|
refreshRunWorkProjection(context, parent.project_id, parent.run_id);
|
|
187
196
|
return { ok: true, work_ids: Object.fromEntries(ids) };
|
|
188
197
|
});
|
|
@@ -205,8 +214,8 @@ export function listWorks(context, input) {
|
|
|
205
214
|
where.push("status = ?");
|
|
206
215
|
params.push(input.status);
|
|
207
216
|
}
|
|
208
|
-
const rows = context.db.all(`SELECT ${workColumns} FROM works WHERE ${where.join(" AND ")} ORDER BY created_at, work_id`, params);
|
|
209
|
-
const ready = (work) => isReady(context, work);
|
|
217
|
+
const rows = context.db.all(`SELECT ${workColumns} FROM works WHERE ${where.join(" AND ")} ORDER BY created_at, work_id`, params).filter(work => !input.workIds || input.workIds.has(work.work_id));
|
|
218
|
+
const ready = (work) => (input.workIds || currentStageWorkIds(context, work.project_id, work.run_id))?.has(work.work_id) !== false && isReady(context, work);
|
|
210
219
|
const filtered = input.ready ? rows.filter(ready) : rows;
|
|
211
220
|
return { works: filtered.slice(0, input.limit ?? filtered.length).map((work) => {
|
|
212
221
|
const isReadyNow = ready(work);
|
|
@@ -382,7 +391,7 @@ export function shortWorkId(id) { return shortEntityReference(id); }
|
|
|
382
391
|
* PATH is not inherited; using the shared lifecycle command keeps them on the
|
|
383
392
|
* same captured runtime as their parent stage.
|
|
384
393
|
*/
|
|
385
|
-
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,
|
|
394
|
+
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, runtimeCommand(context, ["work", "start", shortWorkId(work.work_id), "--project-root", run.project_root, ...(recovery ? ["--recovery-id", recovery.recovery_id] : []), "--json"])); }
|
|
386
395
|
export function assertWorkLaunchReady(context, id) {
|
|
387
396
|
ensureWorkRegistry(context);
|
|
388
397
|
const work = requireWork(context, id);
|
|
@@ -391,7 +400,7 @@ export function assertWorkLaunchReady(context, id) {
|
|
|
391
400
|
throw new AppError("invalid_work_state", "Work is not created", 2, { work_id: id, status: work.status });
|
|
392
401
|
if (!isReady(context, work)) {
|
|
393
402
|
const blockers = readinessBlockers(context, work);
|
|
394
|
-
const ready = context.db.all(`SELECT ${workColumns} FROM works WHERE project_id = ? AND run_id = ? AND status = 'created' ORDER BY created_at, work_id`, [work.project_id, work.run_id]).filter((candidate) => isReady(context, candidate)).map((candidate) => ({ work_id: candidate.work_id, start_command: workStartCommand(context, candidate) }));
|
|
403
|
+
const ready = context.db.all(`SELECT ${workColumns} FROM works WHERE project_id = ? AND run_id = ? AND status = 'created' ORDER BY created_at, work_id`, [work.project_id, work.run_id]).filter((candidate) => currentStageWorkIds(context, candidate.project_id, candidate.run_id)?.has(candidate.work_id) !== false && isReady(context, candidate)).map((candidate) => ({ work_id: candidate.work_id, start_command: workStartCommand(context, candidate) }));
|
|
395
404
|
throw new AppError("work_not_ready", "Work cannot start until dependencies complete and overlapping planned coordination areas are free", 2, { work_id: id, blockers, ready });
|
|
396
405
|
}
|
|
397
406
|
return work;
|
|
@@ -976,8 +985,8 @@ function renderWorkerPrompt(context, work, run, dependencies) {
|
|
|
976
985
|
const command = flowCommand(context);
|
|
977
986
|
const packet = codePacket(work);
|
|
978
987
|
const resultInputPath = path.join(requireRunHome(run), "works", work.work_id, "result-input.json");
|
|
979
|
-
const finishWorkCommand = managedLifecycleCommand(context,
|
|
980
|
-
const failWorkCommand = managedLifecycleCommand(context,
|
|
988
|
+
const finishWorkCommand = managedLifecycleCommand(context, runtimeCommand(context, ["work", "finish", work.work_id, "--result-file", resultInputPath, "--project-root", run.project_root, "--json", "--progress-jsonl"]));
|
|
989
|
+
const failWorkCommand = managedLifecycleCommand(context, runtimeCommand(context, ["work", "fail", shortWorkId(work.work_id), "--reason", "receipt path + exact external or semantic blocker", "--project-root", run.project_root, "--json"]));
|
|
981
990
|
const mergeWork = parsePayload(work)?.kind === "merge";
|
|
982
991
|
const writeBoundary = mergeWork
|
|
983
992
|
? ["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."]
|
|
@@ -1163,7 +1172,7 @@ export function refreshRunWorkProjection(context, projectId, runId) { refreshRun
|
|
|
1163
1172
|
if (fs.existsSync(obsolete))
|
|
1164
1173
|
fs.rmSync(obsolete);
|
|
1165
1174
|
} }
|
|
1166
|
-
export function codeWorkGraph(context, projectId, runId) { const works = context.db.all(`SELECT ${workColumns} FROM works WHERE project_id = ? AND run_id = ? ORDER BY created_at, work_id`, [projectId, runId]).filter((work) => Boolean(codePacket(work))); const ready = works.filter((work) => isReady(context, work)); return { total: works.length, created: works.filter((work) => work.status === "created").length, running: works.filter((work) => work.status === "running").length, completed: works.filter((work) => work.status === "completed").length, failed: works.filter((work) => work.status === "failed").length, ready: ready.map((work) => ({ work_id: work.work_id, task: work.task, launch_policy: work.launch_policy, start_command: workStartCommand(context, work) })), blocked: works.filter((work) => work.status === "created" && !ready.includes(work)).map((work) => ({ work_id: work.work_id, depends_on: parseDependencies(work) })) }; }
|
|
1175
|
+
export function codeWorkGraph(context, projectId, runId) { const index = JSON.parse(context.db.get("SELECT index_json FROM runs WHERE project_id = ? AND id = ?", [projectId, runId])?.index_json ?? "{}"); const active = index.stage_runs?.some(item => item.stage === "code-review" && item.status === "running") ? "code-review" : "code"; const members = index.stage_runs?.length ? stageWorkIds(context, projectId, runId, active) : null; const works = context.db.all(`SELECT ${workColumns} FROM works WHERE project_id = ? AND run_id = ? ORDER BY created_at, work_id`, [projectId, runId]).filter((work) => Boolean(codePacket(work)) && (!members || members.has(work.work_id))); const ready = works.filter((work) => isReady(context, work)); return { total: works.length, created: works.filter((work) => work.status === "created").length, running: works.filter((work) => work.status === "running").length, completed: works.filter((work) => work.status === "completed").length, failed: works.filter((work) => work.status === "failed").length, ready: ready.map((work) => ({ work_id: work.work_id, task: work.task, launch_policy: work.launch_policy, start_command: workStartCommand(context, work) })), blocked: works.filter((work) => work.status === "created" && !ready.includes(work)).map((work) => ({ work_id: work.work_id, depends_on: parseDependencies(work) })) }; }
|
|
1167
1176
|
function parsePayload(work) { if (!work.payload_json)
|
|
1168
1177
|
return null; try {
|
|
1169
1178
|
const value = JSON.parse(work.payload_json);
|
package/dist/storage/database.js
CHANGED
|
@@ -5,6 +5,18 @@ import { createRequire } from "node:module";
|
|
|
5
5
|
import { ensureDir } from "./paths.js";
|
|
6
6
|
import { AppError } from "../shared/errors.js";
|
|
7
7
|
import { executeStoreSchema, readStoreWriterContract, registerStoreWriter } from "./writer-contract.js";
|
|
8
|
+
const workContinuationStorageSql = `
|
|
9
|
+
CREATE TABLE IF NOT EXISTS stage_work_memberships (
|
|
10
|
+
project_id TEXT NOT NULL, run_id TEXT NOT NULL, work_id TEXT NOT NULL REFERENCES works(work_id),
|
|
11
|
+
stage TEXT NOT NULL, attempt TEXT NOT NULL, cycle TEXT NOT NULL DEFAULT '',
|
|
12
|
+
PRIMARY KEY(project_id, run_id, work_id, stage, attempt, cycle)
|
|
13
|
+
);
|
|
14
|
+
CREATE TABLE IF NOT EXISTS repair_intents (
|
|
15
|
+
identity TEXT PRIMARY KEY, project_id TEXT NOT NULL, run_id TEXT NOT NULL,
|
|
16
|
+
stage TEXT NOT NULL, attempt TEXT NOT NULL, payload_hash TEXT NOT NULL,
|
|
17
|
+
payload_json TEXT NOT NULL, work_id TEXT NOT NULL REFERENCES works(work_id), created_at TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
`;
|
|
8
20
|
const require = createRequire(import.meta.url);
|
|
9
21
|
const { DatabaseSync } = require("node:sqlite");
|
|
10
22
|
const resourceDatabases = new Map();
|
|
@@ -210,6 +222,7 @@ export function ensureVnextWorkStorage(db) {
|
|
|
210
222
|
FOREIGN KEY(work_id) REFERENCES works(work_id)
|
|
211
223
|
);
|
|
212
224
|
CREATE INDEX IF NOT EXISTS idx_check_receipts_run ON check_receipts(project_id, run_id, started_at);
|
|
225
|
+
${workContinuationStorageSql}
|
|
213
226
|
`);
|
|
214
227
|
// A committed Work start is authoritative even if publishing its prompt
|
|
215
228
|
// files is interrupted after commit. Keep the exact bytes for repair.
|
|
@@ -949,6 +962,8 @@ function migrate(db) {
|
|
|
949
962
|
CREATE INDEX IF NOT EXISTS idx_check_receipts_run
|
|
950
963
|
ON check_receipts(project_id, run_id, started_at);
|
|
951
964
|
|
|
965
|
+
${workContinuationStorageSql}
|
|
966
|
+
|
|
952
967
|
CREATE TABLE IF NOT EXISTS merge_requests (
|
|
953
968
|
merge_request_id TEXT PRIMARY KEY,
|
|
954
969
|
project_id TEXT NOT NULL,
|