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

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.
Files changed (136) hide show
  1. package/CHANGELOG.md +396 -0
  2. package/README.md +65 -0
  3. package/dist/build-info.json +8 -8
  4. package/dist/cli/command-inputs.js +300 -0
  5. package/dist/cli/help.js +81 -9
  6. package/dist/cli/hook-ingress.js +82 -0
  7. package/dist/cli/input-preparation.js +115 -0
  8. package/dist/cli/run-cli.js +1342 -375
  9. package/dist/cli.js +6 -2
  10. package/dist/harness-runtime/bin/dd-agy.mjs +37 -0
  11. package/dist/harness-runtime/bin/dd-codex.mjs +25 -0
  12. package/dist/harness-runtime/bin/dd-droid.mjs +33 -0
  13. package/dist/harness-runtime/bin/dd-grok.mjs +30 -0
  14. package/dist/harness-runtime/bin/dd-opencode.mjs +21 -0
  15. package/dist/harness-runtime/bin/dd-zcode.mjs +83 -0
  16. package/dist/harness-runtime/lib/daemon-operations.mjs +215 -0
  17. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +423 -0
  18. package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
  19. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  20. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +146 -0
  21. package/dist/harness-runtime/lib/dd-codex.mjs +562 -0
  22. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +125 -0
  23. package/dist/harness-runtime/lib/dd-droid.mjs +471 -0
  24. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +274 -0
  25. package/dist/harness-runtime/lib/dd-grok.mjs +174 -0
  26. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +198 -0
  27. package/dist/harness-runtime/lib/dd-opencode.mjs +98 -0
  28. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +644 -0
  29. package/dist/harness-runtime/lib/dd-zcode.mjs +910 -0
  30. package/dist/harness-runtime/lib/delegation-instructions.d.mts +10 -0
  31. package/dist/harness-runtime/lib/delegation-instructions.mjs +130 -0
  32. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  33. package/dist/harness-runtime/lib/driver-recovery.mjs +129 -0
  34. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  35. package/dist/harness-runtime/lib/managed-daemon.mjs +260 -0
  36. package/dist/harness-runtime/lib/model-observations.mjs +77 -0
  37. package/dist/harness-runtime/lib/native-hook-command.d.mts +1 -0
  38. package/dist/harness-runtime/lib/native-hook-command.mjs +34 -0
  39. package/dist/harness-runtime/lib/observation-clock.mjs +36 -0
  40. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  41. package/dist/harness-runtime/lib/operation-errors.mjs +19 -0
  42. package/dist/harness-runtime/lib/process-json.mjs +70 -0
  43. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  44. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  45. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  46. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  47. package/dist/harness-runtime/lib/tool-observations.d.mts +17 -0
  48. package/dist/harness-runtime/lib/tool-observations.mjs +153 -0
  49. package/dist/schemas/agent-profile.schema.json +1 -1
  50. package/dist/schemas/code-review-result.schema.json +2 -2
  51. package/dist/schemas/code-work-batch.schema.json +4 -3
  52. package/dist/schemas/code-work-result.schema.json +4 -4
  53. package/dist/schemas/plan-review-result.schema.json +2 -2
  54. package/dist/schemas/run-control-receipt.schema.json +99 -0
  55. package/dist/schemas/run-control-request.schema.json +36 -0
  56. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  57. package/dist/services/canon.js +11 -8
  58. package/dist/services/cleanup.js +57 -13
  59. package/dist/services/cli-operation-classifier.js +22 -24
  60. package/dist/services/code-checks.js +543 -96
  61. package/dist/services/codex-hook-delivery.js +28 -0
  62. package/dist/services/config.js +11 -8
  63. package/dist/services/controller-fanout.js +116 -0
  64. package/dist/services/dashboard.js +103 -31
  65. package/dist/services/engines.js +121 -76
  66. package/dist/services/eval-snapshots.js +750 -73
  67. package/dist/services/execution-policy.js +170 -0
  68. package/dist/services/external-work-launch.js +110 -0
  69. package/dist/services/harness-adapter.js +147 -24
  70. package/dist/services/harness-config.js +2 -0
  71. package/dist/services/hooks.js +553 -285
  72. package/dist/services/lanes.js +61 -53
  73. package/dist/services/lifecycle-command.js +87 -7
  74. package/dist/services/lifecycle-invocations.js +828 -0
  75. package/dist/services/managed-daemon-binding.js +38 -0
  76. package/dist/services/managed-processes.js +283 -27
  77. package/dist/services/merge-queue.js +179 -104
  78. package/dist/services/merge-server.js +73 -28
  79. package/dist/services/migrations.js +14 -9
  80. package/dist/services/native-daemon-history.js +49 -0
  81. package/dist/services/native-session-control.js +39 -0
  82. package/dist/services/plan-runtime.js +19 -8
  83. package/dist/services/plans.js +28 -25
  84. package/dist/services/projects.js +18 -5
  85. package/dist/services/prompts.js +23 -16
  86. package/dist/services/protocols.js +64 -20
  87. package/dist/services/recovery-observation-budget.js +61 -0
  88. package/dist/services/recovery-snapshot-database.js +107 -0
  89. package/dist/services/run-control-receipt.js +56 -0
  90. package/dist/services/run-control-worker.js +363 -0
  91. package/dist/services/run-control.js +876 -0
  92. package/dist/services/run-controller-adapter.js +222 -0
  93. package/dist/services/run-controller-capture.js +134 -0
  94. package/dist/services/run-controller-process.js +189 -0
  95. package/dist/services/run-controller-recovery.js +222 -0
  96. package/dist/services/run-controller-state.js +36 -0
  97. package/dist/services/run-controller.js +779 -0
  98. package/dist/services/run-engine-bindings.js +1 -1
  99. package/dist/services/run-fork.js +138 -0
  100. package/dist/services/run-observations.js +112 -0
  101. package/dist/services/run-recovery-runtime.js +69 -0
  102. package/dist/services/run-recovery.js +342 -0
  103. package/dist/services/runs.js +368 -74
  104. package/dist/services/runtime-budget.js +434 -0
  105. package/dist/services/runtime-scope-capture.js +50 -0
  106. package/dist/services/runtime-scope-control.js +450 -0
  107. package/dist/services/runtime-scope-resume.js +565 -0
  108. package/dist/services/runtime-scope-stop.js +99 -0
  109. package/dist/services/runtime-scope-worker.js +243 -0
  110. package/dist/services/runtime-service.js +97 -0
  111. package/dist/services/schema-validation.js +10 -8
  112. package/dist/services/sessions.js +35 -69
  113. package/dist/services/stage-blocker.js +17 -7
  114. package/dist/services/stage-context.js +44 -18
  115. package/dist/services/stage-lifecycle.js +99 -102
  116. package/dist/services/stage-pause.js +65 -23
  117. package/dist/services/usage.js +37 -93
  118. package/dist/services/vnext-code-review.js +189 -85
  119. package/dist/services/vnext-code.js +257 -243
  120. package/dist/services/vnext-execution-profile.js +5 -3
  121. package/dist/services/vnext-fanout.js +134 -19
  122. package/dist/services/vnext-merge.js +285 -102
  123. package/dist/services/vnext-plan-review.js +148 -94
  124. package/dist/services/vnext-plan.js +141 -72
  125. package/dist/services/vnext-protocolize.js +129 -56
  126. package/dist/services/vnext-specify.js +87 -64
  127. package/dist/services/work-registry.js +707 -156
  128. package/dist/services/workspace-bootstrap.js +76 -0
  129. package/dist/services/worktrees.js +85 -35
  130. package/dist/shared/errors.js +12 -0
  131. package/dist/storage/database.js +444 -32
  132. package/dist/storage/paths.js +17 -4
  133. package/dist/storage/writer-contract.js +77 -0
  134. package/dist/storage/writer-migration.js +102 -0
  135. package/package.json +11 -4
  136. package/tools/repair-paused-run-status.mjs +59 -0
@@ -2,12 +2,11 @@ import fs from "node:fs";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { AppError } from "../shared/errors.js";
5
- import { parseJsonObject } from "../shared/json.js";
6
- import { ensureReadableFile } from "../storage/database.js";
7
- import { projectCheckoutRoot, resolveProjectRoot } from "../storage/paths.js";
5
+ import { prepareJsonFile } from "../cli/input-preparation.js";
6
+ import { projectCheckoutRoot, resolveProjectRoot, runtimeStateJsonPath } from "../storage/paths.js";
8
7
  import { appendAudit } from "./audit.js";
9
8
  import { requireProjectByRoot } from "./projects.js";
10
- import { protocolRunDiagnostics, readProtocolRuntimeState, requireProtocol } from "./protocols.js";
9
+ import { protocolRunDiagnostics, readProtocolRuntimeState, repairProtocolRuntimeState, requireProtocol } from "./protocols.js";
11
10
  import { publicSessionIdentity } from "./session-identity.js";
12
11
  const actionKinds = new Set([
13
12
  "repair_protocol_state",
@@ -25,7 +24,8 @@ export function cleanupScan(context, input) {
25
24
  const protocols = context.db.all("SELECT id, status, stage, state_path FROM protocols WHERE project_id = ? ORDER BY id ASC", [project.id]);
26
25
  for (const protocol of protocols) {
27
26
  const terminal = ["closed", "cancelled"].includes(protocol.status) || ["closed", "cancelled"].includes(protocol.stage);
28
- if (!fs.existsSync(protocol.state_path)) {
27
+ const requiresMigration = protocol.state_path !== runtimeStateJsonPath(context.ddFlowHome, project.id, protocol.id);
28
+ if (!fs.existsSync(protocol.state_path) || requiresMigration) {
29
29
  pushFinding(findings, actions, "missing_protocol_runtime_state", "warning", {
30
30
  protocol_id: protocol.id,
31
31
  path: protocol.state_path,
@@ -43,6 +43,8 @@ export function cleanupScan(context, input) {
43
43
  }
44
44
  }
45
45
  const fullProtocol = requireProtocol(context, protocol.id, project.id);
46
+ if (requiresMigration)
47
+ continue;
46
48
  const state = readProtocolRuntimeState(context, fullProtocol).state;
47
49
  const diagnostics = protocolRunDiagnostics(context, fullProtocol, state).diagnostics;
48
50
  for (const diagnostic of diagnostics.filter((entry) => entry.code === "protocol_run_stage_mismatch")) {
@@ -170,20 +172,29 @@ export function cleanupScan(context, input) {
170
172
  plan: { project_root: project.root, project_id: project.id, actions }
171
173
  };
172
174
  }
173
- export function cleanupApply(context, input) {
174
- const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
175
+ export function prepareCleanupApply(context, input) {
175
176
  const reason = input.reason.trim();
176
177
  if (!reason) {
177
178
  throw new AppError("validation", "cleanup apply requires --reason", 2);
178
179
  }
179
- ensureReadableFile(input.planFile);
180
- const plan = validateCleanupPlan(parseJsonObject(fs.readFileSync(input.planFile, "utf8"), input.planFile), project);
180
+ const value = prepareJsonFile(input.planFile, "cleanup plan").value;
181
+ if (!value || typeof value !== "object" || Array.isArray(value))
182
+ throw new AppError("validation", "cleanup plan must be an object", 2);
183
+ const project = requireProjectByRoot(context, resolveProjectRoot(input.projectRoot));
184
+ const plan = validateCleanupPlan(value, project);
181
185
  validateNoDestructiveSurprises(context, plan, input.force);
186
+ return { project, reason, plan };
187
+ }
188
+ export function cleanupApply(context, input) {
189
+ const { project, reason, plan } = input.prepared ?? prepareCleanupApply(context, input);
182
190
  const now = context.now();
183
191
  const results = [];
184
- context.db.exec("BEGIN IMMEDIATE");
192
+ context.db.beginWriteTransaction();
193
+ let executing = false;
185
194
  try {
195
+ validateNoDestructiveSurprises(context, plan, input.force);
186
196
  for (const action of plan.actions) {
197
+ executing = true;
187
198
  results.push(applyAction(context, action, reason, input.force, now));
188
199
  }
189
200
  const changed = results.filter((result) => result.changed).length;
@@ -200,6 +211,8 @@ export function cleanupApply(context, input) {
200
211
  }
201
212
  catch (error) {
202
213
  context.db.exec("ROLLBACK");
214
+ if (executing)
215
+ throw new AppError("cleanup_apply_failed", "Cleanup failed after execution began; inspect retained artifacts before retrying", 1, { effect: "unknown", database_rolled_back: true, attempted_results: results, cause: error instanceof Error ? error.message : String(error), ...(error instanceof AppError ? { cause_code: error.code } : {}) });
203
216
  throw error;
204
217
  }
205
218
  return {
@@ -248,11 +261,29 @@ function validateCleanupPlan(value, project) {
248
261
  throw new AppError("validation", "close_flow_session requires a native session identity", 2);
249
262
  }
250
263
  }
264
+ const requiredField = action.kind === "discard_stale_run" ? "run_id"
265
+ : ["repair_protocol_state", "cancel_queue_job", "close_worktree_record"].includes(String(action.kind)) ? "protocol_id" : undefined;
266
+ if (requiredField && (typeof action[requiredField] !== "string" || !action[requiredField].trim()))
267
+ throw new AppError("validation", `cleanup ${String(action.kind)} requires ${requiredField}`, 2);
268
+ const numericField = action.kind === "expire_lane_lock" ? "lock_id" : action.kind === "expire_lane_waiter" ? "waiter_id" : undefined;
269
+ if (numericField && (!Number.isSafeInteger(action[numericField]) || action[numericField] < 1))
270
+ throw new AppError("validation", `cleanup ${String(action.kind)} requires a positive integer ${numericField}`, 2);
271
+ if (action.worktree_path !== undefined && (typeof action.worktree_path !== "string" || !path.isAbsolute(action.worktree_path)))
272
+ throw new AppError("validation", "cleanup worktree_path must be an absolute path", 2);
251
273
  return action;
252
274
  });
253
275
  return { project_root: project.root, project_id: project.id, actions };
254
276
  }
255
277
  function validateNoDestructiveSurprises(context, plan, force) {
278
+ for (const action of plan.actions) {
279
+ if (action.kind === "repair_protocol_state")
280
+ requireProtocol(context, action.protocol_id, action.project_id);
281
+ if (action.kind === "discard_stale_run") {
282
+ const run = context.db.get("SELECT index_json, status FROM runs WHERE project_id = ? AND id = ?", [action.project_id, action.run_id]);
283
+ if (run && ["running", "discarded"].includes(run.status))
284
+ cleanupRunIndex(run.index_json);
285
+ }
286
+ }
256
287
  const destructive = plan.actions.find((action) => action.kind === "close_worktree_record" && action.worktree_path && dirtyGit(action.worktree_path));
257
288
  if (destructive && !force) {
258
289
  throw new AppError("worktree_dirty", "cleanup apply refuses dirty worktree actions without --force --reason", 1, {
@@ -283,7 +314,7 @@ function applyAction(context, action, reason, force, now) {
283
314
  const protocol = requireProtocol(context, action.protocol_id, action.project_id);
284
315
  const beforePath = protocol.state_path;
285
316
  const existed = fs.existsSync(beforePath);
286
- readProtocolRuntimeState(context, protocol);
317
+ repairProtocolRuntimeState(context, protocol);
287
318
  return {
288
319
  kind: action.kind,
289
320
  changed: !existed || beforePath !== protocol.state_path,
@@ -407,7 +438,7 @@ function applyAction(context, action, reason, force, now) {
407
438
  return { kind: action.kind, changed: false, skipped: true, reason: "invalid_action" };
408
439
  }
409
440
  function discardedRunIndex(indexJson, reason, now) {
410
- const index = JSON.parse(indexJson);
441
+ const index = cleanupRunIndex(indexJson);
411
442
  index.schema_id = "dd-flow/flow-run@3";
412
443
  index.status = "discarded";
413
444
  index.verdict = "discarded";
@@ -417,12 +448,25 @@ function discardedRunIndex(indexJson, reason, now) {
417
448
  index.finished_at ??= now;
418
449
  return index;
419
450
  }
451
+ function cleanupRunIndex(indexJson) {
452
+ try {
453
+ const index = JSON.parse(indexJson);
454
+ if (!index || typeof index !== "object" || Array.isArray(index))
455
+ throw new Error("RUN index must be an object");
456
+ return index;
457
+ }
458
+ catch (error) {
459
+ throw new AppError("runtime_state_invalid", "Cleanup cannot read retained RUN state", 1, { cause: String(error) });
460
+ }
461
+ }
420
462
  function dirtyGit(worktreePath) {
421
463
  if (!fs.existsSync(worktreePath)) {
422
464
  return false;
423
465
  }
424
466
  const result = spawnSync("git", ["-C", worktreePath, "status", "--porcelain"], { encoding: "utf8" });
425
- return result.status === 0 && result.stdout.trim().length > 0;
467
+ if (result.error || result.status !== 0)
468
+ throw new AppError("workspace_status_failed", "Cannot establish worktree status before cleanup", 1, { path: worktreePath, cause: result.error?.message ?? result.stderr });
469
+ return result.stdout.trim().length > 0;
426
470
  }
427
471
  function safeCleanupWorktreePath(context, projectId, worktreePath) {
428
472
  if (!fs.existsSync(worktreePath)) {
@@ -1,3 +1,4 @@
1
+ import { parseCommandArgs } from "./lifecycle-command.js";
1
2
  export function classifyCliOperation(args, env = process.env) {
2
3
  const positional = positionalArgs(args);
3
4
  const [family = null, command = null, action = null] = positional;
@@ -14,19 +15,7 @@ export function classifyCliOperation(args, env = process.env) {
14
15
  return { mode: "normal_write", operation, family, command, action };
15
16
  }
16
17
  function positionalArgs(args) {
17
- const positional = [];
18
- for (let index = 0; index < args.length; index += 1) {
19
- const value = args[index];
20
- if (!value)
21
- continue;
22
- if (value.startsWith("--")) {
23
- if (!value.includes("=") && args[index + 1] && !args[index + 1]?.startsWith("--"))
24
- index += 1;
25
- continue;
26
- }
27
- positional.push(value);
28
- }
29
- return positional;
18
+ return parseCommandArgs(args).positional;
30
19
  }
31
20
  export function isReadOnlyCliOperation(args, env = process.env) {
32
21
  return classifyCliOperation(args, env).mode === "read_only_diagnostics";
@@ -57,7 +46,7 @@ function isMbUpgradeMode(args, env) {
57
46
  }
58
47
  return marked && isUpgradeAllowlisted(args);
59
48
  }
60
- function isUpgradeAllowlisted(args) {
49
+ export function isUpgradeAllowlisted(args) {
61
50
  const positional = positionalArgs(args);
62
51
  const [family, command] = positional;
63
52
  if (family === "status")
@@ -82,14 +71,9 @@ function hasRunBinding(args) {
82
71
  return Boolean(optionValue(args, "run") || positionalArgs(args).some((value) => value.startsWith("RUN-")));
83
72
  }
84
73
  function optionValue(args, name) {
85
- for (let index = args.length - 1; index >= 0; index -= 1) {
86
- const arg = args[index];
87
- if (arg === `--${name}`)
88
- return args[index + 1] ?? null;
89
- if (arg?.startsWith(`--${name}=`))
90
- return arg.slice(name.length + 3);
91
- }
92
- return null;
74
+ const values = parseCommandArgs(args).options.get(name);
75
+ const value = values?.[values.length - 1];
76
+ return value && value !== "__dd_flow_flag__" ? value : null;
93
77
  }
94
78
  function isRouterNative(args) {
95
79
  const first = args[0];
@@ -101,11 +85,17 @@ function isRouterNative(args) {
101
85
  return true;
102
86
  if (["engine", "version", "schema"].includes(first))
103
87
  return true;
88
+ if (first === "storage" && args[1] === "migrate-writer")
89
+ return true;
104
90
  return (first === "codex" && args[1] === "hook" && args[2] === "handle")
91
+ || (first === "zcode" && args[1] === "hook" && args[2] === "handle")
92
+ || (first === "zcode" && args[1] === "invocation" && args[2] === "prepare")
105
93
  || (first === "zcode" && args[1] === "event" && args[2] === "handle")
106
94
  || (first === "zcode" && args[1] === "usage" && args[2] === "ingest")
107
95
  || (first === "grok" && args[1] === "event" && args[2] === "handle")
108
96
  || (first === "grok" && args[1] === "usage" && args[2] === "ingest")
97
+ || (first === "droid" && args[1] === "event" && args[2] === "handle")
98
+ || (first === "droid" && args[1] === "usage" && args[2] === "ingest")
109
99
  || (first === "opencode" && args[1] === "event" && args[2] === "handle")
110
100
  || (first === "opencode" && args[1] === "usage" && args[2] === "ingest")
111
101
  || (first === "agy" && args[1] === "event" && args[2] === "handle")
@@ -119,13 +109,21 @@ function isReadOnlyDiagnostic(family, command, action) {
119
109
  if (family === "canon")
120
110
  return command === "status" || command === "resolve";
121
111
  if (family === "project")
122
- return command === "status" || command === "resolve";
112
+ return command === "status" || command === "resolve" || (command === "config" && action === "status");
123
113
  if (family === "protocol")
124
114
  return ["status", "branch-status", "ready", "blockers", "implement"].includes(command ?? "");
125
115
  if (family === "plan")
126
116
  return command === "status";
127
117
  if (family === "run")
128
- return command === "status" || command === "list";
118
+ return ["status", "list", "timeline"].includes(command ?? "") || (["config", "flags", "drive", "control"].includes(command ?? "") && action === "status") || (command === "vars" && ["ls", "get"].includes(action ?? "")) || (command === "recovery" && action === "inspect");
119
+ if (family === "runtime" && command === "process" && ["status", "check-admission"].includes(action ?? ""))
120
+ return true;
121
+ if (family === "runtime" && command === "scope" && action === "status")
122
+ return true;
123
+ if (family === "work")
124
+ return ["status", "ls", "show"].includes(command ?? "");
125
+ if (family === "stage")
126
+ return command === "fanout" && action === "status";
129
127
  if (family === "lane") {
130
128
  return (command === "status" ||
131
129
  command === "waiters" ||