@deksden-com/dd-flow-cli 0.9.0-beta.7 → 0.9.0-beta.8

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.
@@ -141,7 +141,7 @@ export function submitVnextSpecify(context, input) {
141
141
  throw new AppError("not_found", "--result-file must point to an existing file inside the SPECIFY workspace", 1, { result_file: input.resultFile });
142
142
  }
143
143
  try {
144
- validateSchema({ schemaName: "vnext-specify", file: candidateFile, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id });
144
+ validateSchema({ schemaName: "vnext-specify", file: candidateFile, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: runHome });
145
145
  const result = readVnextSpecifyResult(candidateFile);
146
146
  validateObligations(result, candidateFile);
147
147
  const normalizedResult = `${JSON.stringify(result, null, 2)}\n`;
@@ -169,7 +169,7 @@ export function submitVnextSpecify(context, input) {
169
169
  const htmlPath = path.join(stageRoot, "stage-report.html");
170
170
  const report = buildStageReport({ run, work, workSession, outcome, result, resultMarkdown: renderedMarkdown, now, stageRoot, resultFile });
171
171
  writeStageReport(stageRoot, report);
172
- validateSchema({ schemaName: "stage-report", file: reportPath, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id });
172
+ validateSchema({ schemaName: "stage-report", file: reportPath, projectRoot, ddFlowHome: context.ddFlowHome, runId: run.id, runRoot: runHome });
173
173
  completeFlowRunStage(context, {
174
174
  projectRoot,
175
175
  runId: run.id,
@@ -268,7 +268,7 @@ function readIntake(input) {
268
268
  }
269
269
  function requireRun(context, projectRoot, runId) {
270
270
  const project = requireProjectByRoot(context, projectRoot);
271
- const run = context.db.get("SELECT id, project_id, project_root, workspace_root, run_home_path FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
271
+ const run = context.db.get("SELECT id, project_id, project_root, workspace_root, run_root FROM runs WHERE project_id = ? AND id = ?", [project.id, runId]);
272
272
  if (!run)
273
273
  throw new AppError("not_found", "RUN is not registered", 1, { run_id: runId });
274
274
  return run;
@@ -280,9 +280,9 @@ function runStatus(context, projectId, runId) {
280
280
  return run.status;
281
281
  }
282
282
  function requiredRunHome(run) {
283
- if (!run.run_home_path)
284
- throw new AppError("runtime_missing", "RUN has no portable workspace", 1, { run_id: run.id });
285
- return run.run_home_path;
283
+ if (!run.run_root)
284
+ throw new AppError("runtime_missing", "RUN has no portable artifact root", 1, { run_id: run.id });
285
+ return run.run_root;
286
286
  }
287
287
  function requireWork(context, workId) {
288
288
  const work = context.db.get("SELECT * FROM works WHERE work_id = ?", [workId]);
@@ -423,7 +423,7 @@ function validateWorkResult(work, result, projectRoot, workspaceRoot, runHome, r
423
423
  const candidate = `${resultPath}.candidate-${process.pid}`;
424
424
  fs.writeFileSync(candidate, result);
425
425
  try {
426
- validateSchema({ schemaName: work.result_schema.replace(/^dd-flow\//, "").replace(/@\d+$/, ""), file: candidate, projectRoot, runId });
426
+ validateSchema({ schemaName: work.result_schema.replace(/^dd-flow\//, "").replace(/@\d+$/, ""), file: candidate, projectRoot, runId, runRoot: runHome });
427
427
  }
428
428
  finally {
429
429
  fs.rmSync(candidate, { force: true });
@@ -472,7 +472,7 @@ function validateCodeWorkResult(work, value, projectRoot, runHome, runId) {
472
472
  function renderWorkerPrompt(context, work, run, dependencies) {
473
473
  const command = flowCommand(context);
474
474
  const packet = codePacket(work);
475
- const codeContext = packet ? ["<semantic_spine>", JSON.stringify(packet.semantic_spine, null, 2), "</semantic_spine>", "", ...(packet.repair ? ["<repair_context>", JSON.stringify(packet.repair, null, 2), "Read the failed receipt and its linked stdout/stderr before editing. Preserve the accepted origin context and fix only the evidenced failure.", "</repair_context>", ""] : []), "<accepted_requirements>", JSON.stringify(packet.requirements, null, 2), "</accepted_requirements>", "", "<acceptance_context>", "The criteria below are end-to-end context. Complete this Work's semantic contribution and declared checks; another ordered Work may own a different acceptance surface.", JSON.stringify(packet.acceptance, null, 2), "</acceptance_context>", "", "<required_read>", "These are mandatory starting sources, not a read allowlist. Read any additional project files needed to implement the Work correctly.", ...packet.required_read.map((item) => `- ${resolveRunReferences(item, work.run_id, requireRunHome(run))}`), "</required_read>", "", "<discovery_boundary>", "These are likely discovery areas, not a hard boundary. Expand project-local investigation when required and report material additions.", ...packet.discovery_boundary.map((item) => `- ${item}`), "</discovery_boundary>", "", "<planned_write_areas>", "SOFT COORDINATION HINT ONLY. These paths help the coordinator avoid concurrent collisions. They do not grant or deny write permission and do not limit the files needed for this Work. You may create or change any project file under workspace_root that is necessary and in semantic scope; report every actual changed path.", ...(packet.planned_write_areas.length ? packet.planned_write_areas.map((item) => `- ${item}`) : ["- none predicted; derive the necessary files from the task"]), "</planned_write_areas>", "", ...(packet.provides_checks.length ? ["<provided_checks>", ...packet.provides_checks.map((item) => `- ${item.id}: materialize ${item.command}${item.definition ? ` as ${item.definition}` : ""}; it is not usable until this Work finishes.`), "Update the declared project command or alias before Work finish. The CLI verifies the materialization and then executes the check.", "</provided_checks>", ""] : []), "<verification>", ...packet.checks.map((item) => `- ${item.id} at ${item.run_at}: ${item.command} — ${item.purpose}`), "The CLI executes work-scoped checks and retains their receipts. Report semantic evidence only; do not rerun declared checks manually.", "</verification>", "", "<stop_conditions>", ...packet.stop_conditions.map((item) => `- ${item}`), "</stop_conditions>", ""] : [];
475
+ const codeContext = packet ? ["<semantic_spine>", JSON.stringify(packet.semantic_spine, null, 2), "</semantic_spine>", "", ...(packet.repair ? ["<repair_context>", JSON.stringify(packet.repair, null, 2), "Read the failed receipt and its linked stdout/stderr before editing. Preserve the accepted origin context and fix only the evidenced failure.", "</repair_context>", ""] : []), "<accepted_requirements>", JSON.stringify(packet.requirements, null, 2), "</accepted_requirements>", "", "<write_boundary_invariant>", "For a mutation guarded by membership, ownership, authorization or parent lifecycle state, preserve that predicate in the write statement or make guard and write one explicit transaction with the needed lock. A prior read may diagnose an error but never proves a later write remains allowed. Apply this to create, update, delete and parent-state mutations.", "</write_boundary_invariant>", "", "<acceptance_context>", "The criteria below are end-to-end context. Complete this Work's semantic contribution and declared checks; another ordered Work may own a different acceptance surface.", JSON.stringify(packet.acceptance, null, 2), "</acceptance_context>", "", "<required_read>", "These are mandatory starting sources, not a read allowlist. Read any additional project files needed to implement the Work correctly.", ...packet.required_read.map((item) => `- ${resolveRunReferences(item, work.run_id, requireRunHome(run))}`), "</required_read>", "", "<discovery_boundary>", "These are likely discovery areas, not a hard boundary. Expand project-local investigation when required and report material additions.", ...packet.discovery_boundary.map((item) => `- ${item}`), "</discovery_boundary>", "", "<planned_write_areas>", "SOFT COORDINATION HINT ONLY. These paths help the coordinator avoid concurrent collisions. They do not grant or deny write permission and do not limit the files needed for this Work. You may create or change any project file under workspace_root that is necessary and in semantic scope; report every actual changed path.", ...(packet.planned_write_areas.length ? packet.planned_write_areas.map((item) => `- ${item}`) : ["- none predicted; derive the necessary files from the task"]), "</planned_write_areas>", "", ...(packet.provides_checks.length ? ["<provided_checks>", ...packet.provides_checks.map((item) => `- ${item.id}: materialize ${item.command}${item.definition ? ` as ${item.definition}` : ""}; it is not usable until this Work finishes.`), "Update the declared project command or alias before Work finish. The CLI verifies the materialization and then executes the check.", "</provided_checks>", ""] : []), "<verification>", ...packet.checks.map((item) => `- ${item.id} at ${item.run_at}: ${item.command} — ${item.purpose}`), "The CLI executes work-scoped checks and retains their receipts. Report semantic evidence only; do not rerun declared checks manually.", "</verification>", "", "<stop_conditions>", ...packet.stop_conditions.map((item) => `- ${item}`), "</stop_conditions>", ""] : [];
476
476
  if (packet)
477
477
  codeContext.push("<document_updates>", JSON.stringify(packet.document_updates, null, 2), "Materialize every listed update. dd-flow verifies the resulting file against its PLAN-time baseline.", "</document_updates>", "", "<completion_contract>", "Successful completion requires empty deviations and blockers and every assigned document update in changed_paths. A necessary path outside planned_write_areas is normal coordination drift, not a blocker; include it in changed_paths and continue.", "</completion_contract>", "");
478
478
  return ["<work>", `- work_id: ${work.work_id}`, `- run_id: ${work.run_id}`, `- project_root: ${run.project_root}`, `- workspace_root: ${run.workspace_root}`, `- run_home: ${requireRunHome(run)}`, "</work>", "", "<hard_write_boundary>", `HARD RULE: project source reads and writes must remain under ${run.workspace_root}.`, "Do not write through project_root, outside workspace_root, into another RUN, or into Git/worktree control data. Do not create, switch, merge or delete branches/worktrees.", "RUN artifacts are read-only evidence: refer to them with run:// URIs and let dd-flow persist your submitted result. Accepted requirements, non-goals and stop_conditions are semantic hard boundaries. planned_write_areas is not.", "</hard_write_boundary>", "", ...codeContext, "<dependency_results>", JSON.stringify(dependencies.filter(Boolean), null, 2), "</dependency_results>", "", "<task>", resolveRunReferences(work.task, work.run_id, requireRunHome(run)), "</task>", "", ...(work.result_schema ? ["<result_contract>", `Return JSON matching \`${work.result_schema}\`.`, ...resultSchemaGuidance(work, run.id), "Do not create result.json yourself. Send the JSON to dd-flow on stdin; it atomically validates and stores the canonical receipt.", "</result_contract>", ""] : []), "<completion>", "The CLI runs every declared required check before accepting this Work. A failed receipt means only that the check failed; it is not proof of an engine, harness, dependency, or environment blocker.", "Read the failed receipt and its stdout/stderr. Fix project-owned source, migration, test, formatting, or configuration errors in this same Work, then call Finish again. Do not invent a cause that does not appear in the retained output.", "Use Fail only for a concrete external blocker after deterministic bootstrap or a contradiction with an accepted requirement/non-goal. Never fail merely because a necessary project path was absent from planned_write_areas.", "Finish may run for several minutes. Preserve the shell tool's process/session handle and poll that same invocation until it exits; progress arrives as JSONL on stderr. Never reissue Finish merely because final stdout has not arrived.", `Finish as one standalone command, piping your JSON object to stdin: ${command} work finish ${work.work_id} --result-stdin --project-root ${JSON.stringify(run.project_root)} --json --progress-jsonl`, `Fail only for an evidenced external or semantic-contract blocker: ${command} work fail ${work.work_id} --reason "receipt path + exact external or semantic blocker" --project-root ${JSON.stringify(run.project_root)} --json`, "</completion>", ""].join("\n");
@@ -573,10 +573,10 @@ function requireWork(context, id) { ensureWorkRegistry(context); const exact = c
573
573
  return exact; if (!/^WRK-\d{3,}$/.test(id))
574
574
  throw new AppError("not_found", "Work is not registered", 1, { work_id: id }); const matches = context.db.all(`SELECT ${workColumns} FROM works WHERE work_id LIKE ? ORDER BY work_id`, [`${id}-%`]); if (matches.length !== 1)
575
575
  throw new AppError(matches.length ? "ambiguous_work_alias" : "not_found", matches.length ? "Short Work alias is ambiguous" : "Work is not registered", 1, { work_id: id, matches: matches.map((work) => work.work_id) }); return matches[0]; }
576
- function requireRun(context, projectId, runId) { const run = context.db.get("SELECT r.id, r.run_home_path, r.workspace_root, p.root AS project_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE r.project_id = ? AND r.id = ?", [projectId, runId]); if (!run?.run_home_path)
577
- throw new AppError("runtime_missing", "RUN workspace is unavailable", 1, { run_id: runId }); return run; }
578
- function requireRunHome(run) { if (!run.run_home_path)
579
- throw new AppError("runtime_missing", "RUN workspace is unavailable", 1, { run_id: run.id }); return run.run_home_path; }
576
+ function requireRun(context, projectId, runId) { const run = context.db.get("SELECT r.id, r.run_root, r.workspace_root, p.root AS project_root FROM runs r JOIN projects p ON p.id = r.project_id WHERE r.project_id = ? AND r.id = ?", [projectId, runId]); if (!run?.run_root)
577
+ throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: runId }); return run; }
578
+ function requireRunHome(run) { if (!run.run_root)
579
+ throw new AppError("runtime_missing", "RUN artifact root is unavailable", 1, { run_id: run.id }); return run.run_root; }
580
580
  function parseDependencies(work) { try {
581
581
  const value = JSON.parse(work.depends_on_json);
582
582
  return Array.isArray(value) && value.every((item) => typeof item === "string") ? value : [];
@@ -620,8 +620,8 @@ function readJson(file) { try {
620
620
  catch (error) {
621
621
  throw new AppError("validation", `Invalid JSON file: ${String(error)}`, 2, { file });
622
622
  } }
623
- export function refreshRunWorkProjection(context, projectId, runId) { refreshRunSessionProjection(context, projectId, runId); const run = context.db.get("SELECT run_home_path FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]); if (run?.run_home_path) {
624
- const obsolete = path.join(run.run_home_path, "work.json");
623
+ export function refreshRunWorkProjection(context, projectId, runId) { refreshRunSessionProjection(context, projectId, runId); const run = context.db.get("SELECT run_root FROM runs WHERE project_id = ? AND id = ?", [projectId, runId]); if (run?.run_root) {
624
+ const obsolete = path.join(run.run_root, "work.json");
625
625
  if (fs.existsSync(obsolete))
626
626
  fs.rmSync(obsolete);
627
627
  } }
@@ -564,6 +564,7 @@ function migrate(db, dbPath) {
564
564
  target_branch TEXT NOT NULL,
565
565
  enqueue_target_head TEXT,
566
566
  execution_target_head TEXT,
567
+ accepted_tree TEXT,
567
568
  integration_commit TEXT,
568
569
  execution_route TEXT NOT NULL,
569
570
  status TEXT NOT NULL,
@@ -808,6 +809,8 @@ function migrate(db, dbPath) {
808
809
  db.prepare("UPDATE usage SET cache_read_input_tokens = cached_input_tokens WHERE cache_read_input_tokens IS NULL AND cached_input_tokens IS NOT NULL").run();
809
810
  ensureColumn(db, "runs", "run_home_path", "ALTER TABLE runs ADD COLUMN run_home_path TEXT");
810
811
  ensureColumn(db, "runs", "run_root", "ALTER TABLE runs ADD COLUMN run_root TEXT");
812
+ db.prepare("UPDATE runs SET run_root = COALESCE(run_root, run_home_path, run_dir) WHERE run_root IS NULL OR run_root = ''").run();
813
+ ensureColumn(db, "merge_requests", "accepted_tree", "ALTER TABLE merge_requests ADD COLUMN accepted_tree TEXT");
811
814
  ensureColumn(db, "runs", "layout_version", "ALTER TABLE runs ADD COLUMN layout_version TEXT");
812
815
  ensureColumn(db, "runs", "artifact_root_kind", "ALTER TABLE runs ADD COLUMN artifact_root_kind TEXT");
813
816
  ensureColumn(db, "works", "payload_json", "ALTER TABLE works ADD COLUMN payload_json TEXT");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.7",
3
+ "version": "0.9.0-beta.8",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,15 +16,6 @@
16
16
  "node": ">=26.0.0",
17
17
  "pnpm": ">=10.0.0"
18
18
  },
19
- "scripts": {
20
- "build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
21
- "typecheck": "tsc --noEmit",
22
- "lint": "eslint . --max-warnings=0",
23
- "test": "vitest run",
24
- "changeset": "changeset",
25
- "version-packages": "changeset version",
26
- "release": "pnpm run build && changeset publish"
27
- },
28
19
  "devDependencies": {
29
20
  "@changesets/cli": "^2.31.0",
30
21
  "@eslint/js": "^9.39.1",
@@ -43,5 +34,14 @@
43
34
  "publishConfig": {
44
35
  "access": "public"
45
36
  },
46
- "license": "MIT"
47
- }
37
+ "license": "MIT",
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json && node scripts/generate-build-info.mjs && node scripts/copy-assets.mjs",
40
+ "typecheck": "tsc --noEmit",
41
+ "lint": "eslint . --max-warnings=0",
42
+ "test": "vitest run",
43
+ "changeset": "changeset",
44
+ "version-packages": "changeset version",
45
+ "release": "pnpm run build && changeset publish"
46
+ }
47
+ }