@deksden-com/dd-flow-cli 0.9.0-beta.13 → 0.9.0-beta.134

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 (204) hide show
  1. package/CHANGELOG.md +687 -0
  2. package/README.md +101 -1
  3. package/dist/build-info.json +10 -10
  4. package/dist/cli/argument-values.js +38 -0
  5. package/dist/cli/command-inputs.js +306 -0
  6. package/dist/cli/help.js +88 -12
  7. package/dist/cli/hook-ingress.js +78 -0
  8. package/dist/cli/input-preparation.js +116 -0
  9. package/dist/cli/native-hook-ingress.js +80 -0
  10. package/dist/cli/run-cli.js +1465 -450
  11. package/dist/cli/runtime-maintenance-diagnostics.js +46 -0
  12. package/dist/cli/runtime-maintenance-ingress.js +52 -0
  13. package/dist/cli/runtime-process.js +153 -0
  14. package/dist/cli.js +17 -2
  15. package/dist/domain/stage-catalog.js +1 -1
  16. package/dist/harness-runtime/DD-ZCODE.md +99 -0
  17. package/dist/harness-runtime/bin/dd-agy.mjs +56 -0
  18. package/dist/harness-runtime/bin/dd-codex.mjs +27 -0
  19. package/dist/harness-runtime/bin/dd-droid.mjs +34 -0
  20. package/dist/harness-runtime/bin/dd-grok.mjs +40 -0
  21. package/dist/harness-runtime/bin/dd-opencode.mjs +22 -0
  22. package/dist/harness-runtime/bin/dd-zcode.mjs +87 -0
  23. package/dist/harness-runtime/lib/adapter-timeouts.d.mts +5 -0
  24. package/dist/harness-runtime/lib/adapter-timeouts.mjs +34 -0
  25. package/dist/harness-runtime/lib/agy-hooks.d.mts +1 -0
  26. package/dist/harness-runtime/lib/agy-hooks.mjs +9 -0
  27. package/dist/harness-runtime/lib/codex-capacity-policy.d.mts +10 -0
  28. package/dist/harness-runtime/lib/codex-capacity-policy.mjs +89 -0
  29. package/dist/harness-runtime/lib/daemon-observation.d.mts +2 -0
  30. package/dist/harness-runtime/lib/daemon-observation.mjs +163 -0
  31. package/dist/harness-runtime/lib/daemon-operations.mjs +370 -0
  32. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +6 -0
  33. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +693 -0
  34. package/dist/harness-runtime/lib/dd-agy.mjs +59 -0
  35. package/dist/harness-runtime/lib/dd-codex-daemon.d.mts +1 -0
  36. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +190 -0
  37. package/dist/harness-runtime/lib/dd-codex.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-codex.mjs +805 -0
  39. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +128 -0
  40. package/dist/harness-runtime/lib/dd-droid.mjs +561 -0
  41. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +490 -0
  42. package/dist/harness-runtime/lib/dd-grok.mjs +177 -0
  43. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +255 -0
  44. package/dist/harness-runtime/lib/dd-opencode.mjs +128 -0
  45. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +703 -0
  46. package/dist/harness-runtime/lib/dd-zcode.mjs +1128 -0
  47. package/dist/harness-runtime/lib/delegation-instructions.d.mts +13 -0
  48. package/dist/harness-runtime/lib/delegation-instructions.mjs +144 -0
  49. package/dist/harness-runtime/lib/dispatch-fence.mjs +14 -0
  50. package/dist/harness-runtime/lib/driver-recovery.mjs +139 -0
  51. package/dist/harness-runtime/lib/droid-observation.mjs +66 -0
  52. package/dist/harness-runtime/lib/lease-renewal.d.mts +8 -0
  53. package/dist/harness-runtime/lib/lease-renewal.mjs +36 -0
  54. package/dist/harness-runtime/lib/lifecycle-command.d.mts +20 -0
  55. package/dist/harness-runtime/lib/lifecycle-command.mjs +272 -0
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +10 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +19 -0
  58. package/dist/harness-runtime/lib/managed-daemon.d.mts +12 -0
  59. package/dist/harness-runtime/lib/managed-daemon.mjs +619 -0
  60. package/dist/harness-runtime/lib/model-observations.mjs +101 -0
  61. package/dist/harness-runtime/lib/native-children.d.mts +14 -0
  62. package/dist/harness-runtime/lib/native-children.mjs +94 -0
  63. package/dist/harness-runtime/lib/native-hook-command.d.mts +7 -0
  64. package/dist/harness-runtime/lib/native-hook-command.mjs +251 -0
  65. package/dist/harness-runtime/lib/native-streams.mjs +45 -0
  66. package/dist/harness-runtime/lib/observation-clock.d.mts +18 -0
  67. package/dist/harness-runtime/lib/observation-clock.mjs +92 -0
  68. package/dist/harness-runtime/lib/operation-context.mjs +5 -0
  69. package/dist/harness-runtime/lib/operation-errors.d.mts +14 -0
  70. package/dist/harness-runtime/lib/operation-errors.mjs +116 -0
  71. package/dist/harness-runtime/lib/operation-progress.d.mts +7 -0
  72. package/dist/harness-runtime/lib/operation-progress.mjs +112 -0
  73. package/dist/harness-runtime/lib/process-json.d.mts +5 -0
  74. package/dist/harness-runtime/lib/process-json.mjs +201 -0
  75. package/dist/harness-runtime/lib/process-snapshot.mjs +10 -0
  76. package/dist/harness-runtime/lib/runner-events.mjs +200 -0
  77. package/dist/harness-runtime/lib/runner-lock.mjs +42 -0
  78. package/dist/harness-runtime/lib/session-settlement.mjs +30 -0
  79. package/dist/harness-runtime/lib/setup-progress.d.mts +9 -0
  80. package/dist/harness-runtime/lib/setup-progress.mjs +51 -0
  81. package/dist/harness-runtime/lib/tool-observations.d.mts +21 -0
  82. package/dist/harness-runtime/lib/tool-observations.mjs +228 -0
  83. package/dist/harness-runtime/lib/zcode-usage.mjs +55 -0
  84. package/dist/protocol/local-files.js +18 -9
  85. package/dist/runtime/context.js +8 -4
  86. package/dist/schemas/agent-profile.schema.json +1 -1
  87. package/dist/schemas/code-review-result.schema.json +1 -1
  88. package/dist/schemas/code-verification.schema.json +7 -5
  89. package/dist/schemas/code-work-batch.schema.json +2 -2
  90. package/dist/schemas/flow-run.schema.json +2 -0
  91. package/dist/schemas/plan-review-decision.schema.json +3 -3
  92. package/dist/schemas/plan-review-result.schema.json +2 -2
  93. package/dist/schemas/run-control-receipt.schema.json +118 -0
  94. package/dist/schemas/run-control-request.schema.json +36 -0
  95. package/dist/schemas/vnext-protocol-plan.schema.json +2 -2
  96. package/dist/services/branch-context.js +3 -3
  97. package/dist/services/canon.js +11 -8
  98. package/dist/services/check-runner-evidence.js +121 -0
  99. package/dist/services/cleanup.js +133 -40
  100. package/dist/services/cli-operation-classifier.js +22 -24
  101. package/dist/services/code-checks.js +851 -109
  102. package/dist/services/codex-capacity.js +367 -0
  103. package/dist/services/codex-hook-delivery.js +32 -0
  104. package/dist/services/command-context.js +80 -0
  105. package/dist/services/compatibility-preflight.js +13 -3
  106. package/dist/services/config.js +11 -8
  107. package/dist/services/continuation-outcome.js +20 -0
  108. package/dist/services/controller-fanout.js +136 -0
  109. package/dist/services/dashboard.js +135 -56
  110. package/dist/services/engines.js +245 -119
  111. package/dist/services/eval-snapshots.js +1146 -105
  112. package/dist/services/execution-policy.js +181 -0
  113. package/dist/services/external-work-launch.js +230 -0
  114. package/dist/services/harness-adapter.js +329 -36
  115. package/dist/services/harness-config.js +2 -0
  116. package/dist/services/hooks.js +753 -325
  117. package/dist/services/lanes.js +78 -65
  118. package/dist/services/lifecycle-command.js +1 -288
  119. package/dist/services/lifecycle-contract.js +1 -0
  120. package/dist/services/lifecycle-invocations.js +1828 -0
  121. package/dist/services/managed-daemon-binding.js +38 -0
  122. package/dist/services/managed-lease-monitor.js +133 -0
  123. package/dist/services/managed-processes.js +409 -51
  124. package/dist/services/merge-queue.js +228 -149
  125. package/dist/services/merge-server.js +163 -35
  126. package/dist/services/migrations.js +37 -16
  127. package/dist/services/native-daemon-history.js +49 -0
  128. package/dist/services/native-session-control.js +39 -0
  129. package/dist/services/plan-runtime.js +26 -11
  130. package/dist/services/plans.js +28 -25
  131. package/dist/services/projects.js +20 -15
  132. package/dist/services/prompts.js +28 -17
  133. package/dist/services/protocols.js +163 -87
  134. package/dist/services/recovery-observation-budget.js +121 -0
  135. package/dist/services/recovery-snapshot-database.js +145 -0
  136. package/dist/services/repair-intents.js +42 -0
  137. package/dist/services/review-copy.js +157 -0
  138. package/dist/services/review-deferral.js +23 -0
  139. package/dist/services/run-control-receipt.js +56 -0
  140. package/dist/services/run-control-worker.js +411 -0
  141. package/dist/services/run-control.js +985 -0
  142. package/dist/services/run-controller-adapter.js +399 -0
  143. package/dist/services/run-controller-capture.js +179 -0
  144. package/dist/services/run-controller-process.js +187 -0
  145. package/dist/services/run-controller-recovery.js +289 -0
  146. package/dist/services/run-controller-state.js +97 -0
  147. package/dist/services/run-controller.js +1035 -0
  148. package/dist/services/run-engine-bindings.js +1 -1
  149. package/dist/services/run-fork.js +147 -0
  150. package/dist/services/run-observations.js +152 -0
  151. package/dist/services/run-projection.js +22 -64
  152. package/dist/services/run-recovery-guard.js +75 -0
  153. package/dist/services/run-recovery-runtime.js +112 -0
  154. package/dist/services/run-recovery.js +349 -0
  155. package/dist/services/runs.js +558 -217
  156. package/dist/services/runtime-budget.js +430 -0
  157. package/dist/services/runtime-command.js +43 -0
  158. package/dist/services/runtime-scope-capture.js +52 -0
  159. package/dist/services/runtime-scope-control.js +468 -0
  160. package/dist/services/runtime-scope-resume.js +599 -0
  161. package/dist/services/runtime-scope-stop.js +180 -0
  162. package/dist/services/runtime-scope-worker.js +287 -0
  163. package/dist/services/runtime-service.js +96 -0
  164. package/dist/services/schema-validation.js +10 -8
  165. package/dist/services/scope-writer-cleanup.js +27 -0
  166. package/dist/services/sessions.js +35 -69
  167. package/dist/services/stage-blocker.js +17 -7
  168. package/dist/services/stage-context.js +185 -19
  169. package/dist/services/stage-lifecycle.js +98 -107
  170. package/dist/services/stage-pause.js +89 -75
  171. package/dist/services/stage-report-renderer.js +158 -6
  172. package/dist/services/stage-work-graph.js +35 -0
  173. package/dist/services/tooling.js +32 -23
  174. package/dist/services/usage.js +49 -101
  175. package/dist/services/verification-matrix.js +374 -0
  176. package/dist/services/vnext-code-review.js +222 -87
  177. package/dist/services/vnext-code.js +324 -251
  178. package/dist/services/vnext-contracts.js +10 -1
  179. package/dist/services/vnext-execution-profile.js +79 -4
  180. package/dist/services/vnext-fanout.js +182 -17
  181. package/dist/services/vnext-merge.js +335 -113
  182. package/dist/services/vnext-plan-review.js +276 -126
  183. package/dist/services/vnext-plan.js +227 -102
  184. package/dist/services/vnext-protocolize.js +335 -82
  185. package/dist/services/vnext-specify.js +119 -81
  186. package/dist/services/work-registry.js +1083 -194
  187. package/dist/services/workspace-bootstrap.js +87 -0
  188. package/dist/services/workspace-files.js +85 -0
  189. package/dist/services/worktrees.js +98 -44
  190. package/dist/shared/entity-references.js +8 -0
  191. package/dist/shared/errors.js +17 -0
  192. package/dist/storage/agent-output.js +26 -0
  193. package/dist/storage/database.js +785 -37
  194. package/dist/storage/foreign-key-transaction.js +39 -0
  195. package/dist/storage/immutable-input.js +45 -0
  196. package/dist/storage/import-guard.js +102 -0
  197. package/dist/storage/native-cleanup.js +52 -0
  198. package/dist/storage/paths.js +17 -4
  199. package/dist/storage/projection-lock.js +73 -0
  200. package/dist/storage/work-references.js +23 -0
  201. package/dist/storage/writer-contract.js +77 -0
  202. package/dist/storage/writer-migration.js +85 -0
  203. package/package.json +30 -13
  204. package/tools/repair-paused-run-status.mjs +84 -0
package/README.md CHANGED
@@ -1,9 +1,78 @@
1
1
  # dd-flow-cli
2
2
 
3
+ See [dd-zcode integration](src/harness-runtime/DD-ZCODE.md) for native transport,
4
+ adapter ownership and the versioned bridge contract.
5
+
6
+ ## Native daemon shutdown
7
+
8
+ `daemon.stop` fences productive dispatch before awaiting settlement. A successful
9
+ durable stop receipt follows native tree settlement, physical provider exit,
10
+ journal drain and required resource/auth finalization. The endpoint then retires
11
+ before ACK; clients also verify the retained daemon PID has exited. A missing
12
+ socket alone is not physical shutdown proof.
13
+
14
+ Partial cleanup preserves the same incarnation's completed phases in
15
+ `daemon.json` (`dd-flow/daemon-shutdown@1`). A fresh control operation can finish
16
+ remaining cleanup without replaying agent work or inspecting a closed pipe.
17
+ Failed operation IDs remain immutable. Unknown ownership and persistent EPERM
18
+ remain failures; never remove sockets or repair runtime databases manually to
19
+ make an outcome look clean.
20
+
21
+ Shutdown proof records `required_phases`; every recorded phase must be complete,
22
+ including native tree and daemon resource settlement. A `clean` label without
23
+ this proof cannot authorize retained restart. `stopping`, `cleanup_failed` and
24
+ `unclean` cannot start a replacement native bridge. Forced AGY termination
25
+ without a current native terminal receipt is unclean, even after a previous
26
+ successful Turn. Mandatory stream-journal failures remain failures after
27
+ physical cleanup. Listener retirement must not unlink a replacement endpoint.
28
+
29
+ ## Managed temporary HTTP services
30
+
31
+ For an interactive scenario, `runtime process start --run <RUN-ID>
32
+ --project-root <absolute-project> --command '<project command>' --ports api
33
+ --ready-port api --ready-path /health --json --progress-jsonl` runs a foreground
34
+ supervisor. The command receives `DD_FLOW_PORT_API`; use it instead of a fixed
35
+ port. Additional comma-separated names receive equivalent environment variables.
36
+ The ready event points to a receipt and logs under the RUN's `runtime/` folder.
37
+ Keep the tool invocation alive, run the scenario using the allocated ports,
38
+ then execute the exact `stop_command` in that receipt and await the supervisor.
39
+ Readiness is not a scenario pass. Keep semantic/scenario evidence separately.
40
+ Never use a broad `pkill`/`killall`. A failed owned stop retains resources.
41
+
3
42
  `dd-flow-cli` is the mechanical control layer for `dd-flow` workflows.
4
43
 
5
44
  It does not replace Memory Bank prompts and does not make product, design, merge, or verification judgments. Prompts own intent, route selection, planning depth, evidence meaning, and semantic readiness. The CLI owns explicit local state: projects, protocols, Codex session bindings, transitions, worktree records, lanes, locks, merge queue jobs, hook records, and audit events.
6
45
 
46
+ ## Factory Droid harness
47
+
48
+ Configure `droid-cli` in `~/.dd-flow/harnesses.json` with absolute
49
+ `adapter_command` (`dd-droid`) and `runtime_command` (`droid`) paths. Agent
50
+ profiles use the `droid` alias; the adapter receives `--droid-bin`.
51
+
52
+ The controlled adapter sends `dd-flow/droid-tool-event@1` JSON to
53
+ `dd-flow droid event handle --project-root <root> --json`. Events include a
54
+ stable `event_id`, `daemon_id`, `phase` (`before` or `after`), native Session
55
+ identity and parent, absolute Session directory, `Execute` input, observed
56
+ profile and transcript path. Before a lifecycle invocation, the returned
57
+ `hookSpecificOutput.updatedInput` adds its one-use `--hook-event-id` receipt.
58
+ The event ID belongs to the native hook invocation and is reused only for
59
+ transport retries. Session success is established by the Work contract.
60
+
61
+ `dd-flow droid usage ingest --project-root <root> --json` accepts cumulative
62
+ per-Session counters with explicit `usage_scope: physical_session` and
63
+ `completeness: complete|partial`. Inclusive root counters are rejected;
64
+ partial windows remain unavailable in RUN totals until complete evidence
65
+ arrives. Keep inclusive provider totals in adapter evidence for reconciliation.
66
+
67
+ ## Check evidence
68
+
69
+ Checks write evidence into their invocation's `DD_FLOW_EVIDENCE_DIR`. Receipts
70
+ retain and hash every regular file in that directory, including nested files.
71
+ `required_artifacts` specifies the minimum files needed to pass, so an empty
72
+ list still permits supplemental screenshots or reports. Missing required files
73
+ fail the check. Symlinks are not followed or registered. The same collection
74
+ rules apply when recovering a detached check from its completion marker.
75
+
7
76
  ## Runtime Model
8
77
 
9
78
  Root local database:
@@ -402,10 +471,41 @@ remain invalid and receive a standalone retry.
402
471
  Local verification for this repository:
403
472
 
404
473
  ```bash
405
- pnpm test
406
474
  pnpm typecheck
407
475
  pnpm lint
408
476
  pnpm build
477
+ pnpm test
409
478
  ```
410
479
 
480
+ Build once before testing: release contracts and native fixtures verify the
481
+ copied `dist` bytes. `pnpm test` remains release → integration → runtime-sensitive.
482
+ For development use `pnpm test:fast`, `pnpm test:core` or `pnpm test:flow` plus
483
+ the affected native contracts; these subsets are not full acceptance.
484
+ New files default to serial core execution. A forced serial fallback is
485
+ `pnpm test:integration --no-file-parallelism --maxWorkers=1`.
486
+ The [test-suite verification runbook](https://github.com/deksden-com/dd-eval/blob/docs/066-test-suite-optimization/runbooks/flow-test-suite-verification.md)
487
+ owns group qualification, complete reports and cross-repository commands.
488
+
411
489
  Manual smoke checks should use a temporary `DD_FLOW_HOME` when they do not intentionally inspect the operator's real local state.
490
+
491
+ ### RUN status during recovery
492
+
493
+ A controller failure pauses the logical RUN without rewriting its Stage/Work
494
+ outcomes. Accepted pause/stop or recovery intent immediately persists `paused` in the RUN row,
495
+ `index_json`, and `run.json`. A trusted recovery acknowledgement restores the
496
+ operational status; an existing Stage/Work pause remains paused. Terminal RUNs
497
+ are never reopened. Physical tree settlement remains a separate control receipt.
498
+
499
+ Legacy stores with a fenced recovery may still contain `running`. After building,
500
+ inspect one exact RUN with:
501
+
502
+ ```sh
503
+ node tools/repair-paused-run-status.mjs --home /path/to/dd-flow-home --project PRJ-001-project --run RUN-001-eval-subject
504
+ ```
505
+
506
+ Add `--apply` to repair it. This requires the latest recovery to be draining, sealed or resuming, saves the original row and runtime JSON
507
+ beside `run.json`, and changes no schema, engine binding or recovery capture.
508
+ It does not claim physical settlement or alter its receipts. `--cancel-abandoned`
509
+ explicitly cancels an abandoned fixture only when the project directory is absent
510
+ and it has no Work, Session, controller or recovery history. Historical experiment
511
+ engine snapshots remain pinned; rebuild/release the engine for future executions.
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.13",
4
- "cli_commit": "020ada108a59291f4462f7d52d3ea64bccd77b56",
5
- "built_at": "2026-09-05T08:17:23.882Z",
3
+ "cli_version": "0.9.0-beta.134",
4
+ "cli_commit": "b243f4dac38629b7f0398b3e0b1613904f4aebff",
5
+ "built_at": "2026-10-13T11:24:52.803Z",
6
6
  "built_with_canon": {
7
- "version": null,
8
- "commit": null,
9
- "flow_contract": null,
10
- "repo_root": null,
11
- "memorybank_root": null,
12
- "flow_root": null,
13
- "layout": null
7
+ "version": "4.1.2",
8
+ "commit": "2ba0b9ceedf8e4544ad9566c240ef37ea5e8d099",
9
+ "flow_contract": "dd-flow-canonical-2026-08",
10
+ "repo_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-081-canon",
11
+ "memorybank_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-081-canon/.memory-bank",
12
+ "flow_root": "/Users/deksden/Documents/_Projects/_worktrees/dd-memorybank-081-canon/.memory-bank/dd-flow",
13
+ "layout": "dot_memory_bank"
14
14
  }
15
15
  }
@@ -0,0 +1,38 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ const flagValue = "__dd_flow_flag__";
3
+ export function optionalOption(parsed, key) {
4
+ const values = parsed.options.get(key);
5
+ const value = values?.[values.length - 1];
6
+ if (value === flagValue)
7
+ throw new AppError("usage", `--${key} requires a value`, 2);
8
+ if (value !== undefined && !value.trim())
9
+ throw new AppError("usage", `--${key} requires a non-empty value`, 2);
10
+ return value;
11
+ }
12
+ export function requiredOption(parsed, key) {
13
+ const value = optionalOption(parsed, key);
14
+ if (!value) {
15
+ throw new AppError("usage", `Missing required option: --${key}`, 2);
16
+ }
17
+ return value;
18
+ }
19
+ export function optionalNumber(parsed, key) {
20
+ const value = optionalOption(parsed, key);
21
+ if (typeof value === "undefined") {
22
+ return undefined;
23
+ }
24
+ const parsedNumber = Number(value);
25
+ if (!Number.isFinite(parsedNumber)) {
26
+ throw new AppError("validation", `--${key} must be a number`, 2);
27
+ }
28
+ return parsedNumber;
29
+ }
30
+ export function optionalPositiveNumber(parsed, key, integer = false) {
31
+ const value = optionalNumber(parsed, key);
32
+ if (typeof value === "undefined")
33
+ return undefined;
34
+ if (value <= 0 || (integer && !Number.isInteger(value))) {
35
+ throw new AppError("validation", `--${key} must be a positive${integer ? " integer" : ""} number`, 2);
36
+ }
37
+ return value;
38
+ }
@@ -0,0 +1,306 @@
1
+ import { AppError } from "../shared/errors.js";
2
+ import { parseCommandArgs } from "../services/lifecycle-command.js";
3
+ import { lifecycleInternalOptions, lifecycleRouteForOperation } from "../services/lifecycle-contract.js";
4
+ import { isUpgradeAllowlisted } from "../services/cli-operation-classifier.js";
5
+ const words = (value) => value.split(" ").filter(Boolean);
6
+ const route = (positions, required = "", values = "", switches = "", repeatable = "", enums = {}) => ({
7
+ positions: typeof positions === "number" ? [positions, positions] : positions,
8
+ required: words(required), values: words(`${required} ${values}`), switches: words(switches), repeatable: words(repeatable), enums
9
+ });
10
+ const project = "project-root";
11
+ const scoped = "project-root"; // Optional when the router resolves a unique retained scope.
12
+ const lifecycle = lifecycleInternalOptions.join(" ");
13
+ const worker = "worker-id";
14
+ const control = "project-root run";
15
+ /** Path-shaped arguments default to physical paths unless the route explicitly
16
+ * opts into contextualPaths. This inventory does not expand arbitrary strings. */
17
+ export const physicalPathOptions = new Set(words("root project-root workspace-root workspace-path path dir file schema-dir output result-file semantic-file data decision-file verification-file reason-file task-file intake-file context-file observations-file observation-file summary-file question-file answer-file payload-file json-file task-profile-file execution-routing-file protocol-override-file manifest-file routing-file config-file input-file report-file snapshot-file output-dir state-dir capture-root capture-path destination source archive snapshot plan-file backup-path database backup source-home target-home stdout stderr adapter-executable memory-bank flow stage-report dashboard report"));
18
+ physicalPathOptions.add("operational-decision-file");
19
+ export const commandInputs = {
20
+ "version": route(0),
21
+ "status": route(0, "", "root project-root", "check-registry"),
22
+ "project register": route(0, "root"),
23
+ "project status": route(0, "root"),
24
+ "project summary": route(0, project, "write"),
25
+ "project resolve": route(1),
26
+ "project archive": route([0, 1], "reason", "root"),
27
+ "project migrate-ids": route(0, "root", "", "apply"),
28
+ "project config status": route(0, project),
29
+ "project config set": route(0, `${project} key value`),
30
+ "protocol register": route(1, project, "workspace-path"),
31
+ "protocol status": route(1, "", scoped),
32
+ "protocol branch-status": route([0, 1], "", `${scoped} path`),
33
+ "protocol ready": route(0, project),
34
+ "protocol blockers": route(1, project),
35
+ "protocol implement": route(1, "", `${scoped} reason`, "force"),
36
+ "protocol ready-for-merge": route(1, "", scoped),
37
+ "protocol transition": route(1, "to", `${scoped} payload-file json-file reason`, "force"),
38
+ "transition": route(1, "to", `${scoped} payload-file json-file reason`, "force"),
39
+ "protocol sync-from-run": route(1, "run", `${scoped} target`),
40
+ "protocol cancel": route(1, "reason", `${scoped} close-sessions cancel-queue release-locks worktree`, "force"),
41
+ "plan status": route(1, "", scoped),
42
+ "plan reviews": { ...route(0), retired: "PLAN review dispatch moved to: dd-flow plan-review dispatch <RUN>" },
43
+ "plan item start": route(2, "", scoped),
44
+ "plan item done": route(2, "summary", `${scoped} evidence`, "", "evidence"),
45
+ "plan item block": route(2, "reason user-required", scoped),
46
+ "plan item skip": route(2, "reason", scoped),
47
+ "plan-review dispatch": route(1, "", scoped),
48
+ "prompt render": { ...route(0, `${project} run stage profile`, "workspace-root task-file plan-item"), contextualPaths: words("project-root workspace-root task-file") },
49
+ "stage start": { ...route([0, 1], `${project} stage`, `${lifecycle} dir subject intake-file context-file context-sha256`, "bootstrap intake-stdin require-session-binding"), contextualPaths: words("project-root intake-file context-file") },
50
+ "stage finish": { ...route(1, `${project} stage`, `${lifecycle} dir semantic-file data result-file outcome decision-file verification-file retry-check reason reason-file request work`, "result-stdin"), contextualPaths: words("project-root semantic-file result-file decision-file verification-file reason-file") },
51
+ "stage pause": { ...route(1, `${project} stage work`, `${lifecycle} question-file`, "question-stdin"), contextualPaths: words("project-root question-file") },
52
+ "stage resume": { ...route(1, `${project} stage work`, `${lifecycle} answer-file`, "answer-stdin"), contextualPaths: words("project-root answer-file") },
53
+ "stage block": { ...route(1, `${project} stage work kind code`, "summary-file", "summary-stdin retryable", "", { kind: ["engine", "harness", "environment"] }), contextualPaths: words("project-root summary-file") },
54
+ "stage unblock": route(1, `${project} stage work`),
55
+ "stage fanout status": route(1, "stage", scoped),
56
+ "stage fanout dispatch": route(1, "stage", scoped),
57
+ "stage fanout reconcile": { ...route(1, "stage observations-file", scoped), contextualPaths: words("project-root observations-file") },
58
+ "stage native observe": { ...route(1, "stage observations-file", scoped), contextualPaths: words("project-root observations-file") },
59
+ "work status": route(1, project),
60
+ "work add-batch": { ...route(0, "parent file", scoped), contextualPaths: words("project-root file") },
61
+ "work ls": route(0, "", `${scoped} run parent status limit`, "ready include-results", "", { status: ["created", "running", "paused", "completed", "failed", "cancelled"] }),
62
+ "work show": route(1, "", scoped),
63
+ "work repair add": { ...route(0, "run", `${scoped} origin-work from-check from-finding from-unresolved check-ref verification-file task-file`, "task-stdin", "origin-work from-finding from-unresolved check-ref"), contextualPaths: words("project-root verification-file task-file") },
64
+ "work deps add": route(1, "on", scoped, "", "on"),
65
+ "work deps remove": route(1, "on", scoped, "", "on"),
66
+ "work deps list": route(1, "", scoped),
67
+ "work deps clear": route(1, "", scoped),
68
+ "work delete": route(1, "", scoped),
69
+ "work launch": route(1, "", `${scoped} stage`),
70
+ "work start": { ...route(1, "", `${scoped} ${lifecycle} recovery-id`), contextualPaths: ["project-root"] },
71
+ "work finish": { ...route(1, "", `${scoped} ${lifecycle} result-file retry-check reason reason-file`, "result-stdin"), contextualPaths: words("project-root result-file reason-file") },
72
+ "work fail": { ...route(1, "reason", `${scoped} ${lifecycle}`), contextualPaths: ["project-root"] },
73
+ "work cancel": route(1, "reason", scoped),
74
+ "work retry": route(1, "reason", scoped),
75
+ "session register": { ...route(0, "", `${scoped} ${lifecycle} payload-base64 payload-json payload-file session-id`), contextualPaths: words("project-root payload-file") },
76
+ "session status": route(0, project, "session-id worker-id"),
77
+ "session stop": route(0, `${project} session-id reason`),
78
+ "session stop-worker": route(0, `${project} worker-id reason`),
79
+ "session usage sync": route(0, project, "session-id"),
80
+ "run start": route(0, `${project} flow-kind subject-type subject-id slug`, "workspace-root next-action preset task-profile-file execution-routing-file protocol-override-file flag", "", "flag"),
81
+ "run prepare-vnext-specify": route(0, `${project} slug`, "execution-routing-file"),
82
+ "run status": route(1, project),
83
+ "run list": route(0, project),
84
+ "run timeline": route(1, project, "hide", "", "hide"),
85
+ "run config status": route(1, project),
86
+ "run config set": route(1, `${project} key value reason`),
87
+ "run vars ls": route(1, project),
88
+ "run vars get": route(1, `${project} key`),
89
+ "run vars set": route(1, `${project} key value`),
90
+ "run flags status": route(1, project),
91
+ "run flags revise": route(1, `${project} expected-revision idempotency-key`, "flag preset reason", "allow-downgrade", "flag"),
92
+ "run attach-stage": route(1, `${project} stage dir status`, "data-schema-id"),
93
+ "run complete-stage": route(1, `${project} stage status`, "stage-report dashboard data data-schema-id report alias", "", "alias"),
94
+ "run complete": { ...route(0), retired: "run complete is retired; only an operator may use run override" },
95
+ "run override": route(1, `${project} status reason`, "verdict next-action", "", "", { status: ["cancelled", "failed"] }),
96
+ "run capacity record": route(1, `${project} available-slots`),
97
+ "run recovery inspect": route(1, project),
98
+ "run recovery begin": route(1, `${project} interruption-json`),
99
+ "run recovery seal": route(1, `${project} recovery-id settlement-json`),
100
+ "run recovery capture": route(1, `${project} recovery-id capture-path`),
101
+ "run recovery resume": route(1, `${project} recovery-id daemon-id harness native-session-id`),
102
+ "run recovery accept": { ...route(1, `${project} recovery-id`, lifecycle), contextualPaths: ["project-root"] },
103
+ "run snapshot bootstrap create": route(0, `${project} output`),
104
+ "run snapshot bootstrap restore": route(0, `${project} snapshot`),
105
+ "run snapshot create": route(1, `${project} output`, "recovery-id stage-entry", "candidate incomplete"),
106
+ "run snapshot restore": route(0, `${project} snapshot`, "execution-routing-file operational-decision-file recovery-id"),
107
+ "run fork": route(0, "from output engine-version request-id", "integrity-checksum operational-decision-file"),
108
+ "run drive status": route(0, control, "after"),
109
+ "run drive launch": route(0, `${control} request-id context-file context-sha256`, "stop-after capture-root"),
110
+ "run drive serve": route(0, `${control} controller-id`),
111
+ "run drive answer": route(0, `${control} request-id pause-id answer-file`),
112
+ "run drive context": route(0, `${control} controller-id request-id stage context-file context-sha256`),
113
+ "run control status": route(0, control),
114
+ "run control reconcile": route(0, `${control} control-id request-id`),
115
+ "run control resume": route(0, `${control} request-id from`, "wait-ms"),
116
+ "run control serve": route(0, `${control} control-id`),
117
+ "run control pause": route(0, `${control} request-id`, "grace-ms wait-ms escalate", "force"),
118
+ "run control stop": route(0, `${control} request-id`, "grace-ms wait-ms escalate", "force"),
119
+ "runtime scope fence": route(0, "scope-id request-id"),
120
+ "runtime scope status": route(0, "scope-id"),
121
+ "runtime scope stop": route(0, "scope-id request-id"),
122
+ "runtime scope control": route(0, "scope-id request-id mode", "manifest-json", "", "", { mode: ["pause", "stop"] }),
123
+ "runtime scope serve": route(0, "scope-id generation"),
124
+ "runtime scope resume": route(0, "scope-id request-id generation capture-key"),
125
+ "runtime scope reconcile": route(0, "scope-id generation request-id"),
126
+ "runtime process check-admission": route(0, "id lease-token"),
127
+ "runtime process start": route(0, `${control} command ports ready-port`, "ready-path ready-timeout-ms"),
128
+ "runtime process stop": route(0, "id lease-token", "grace-ms"),
129
+ "runtime process register": route(0, "kind owner", "id lease-ms budget-json project-id run pid process-group-id owner-pid work check operation stdout stderr harness dispatch-barrier role state-dir adapter-executable"),
130
+ "runtime process confirm": route(0, "id lease-token pid", "owner-pid lease-ms process-group-id"),
131
+ "runtime process heartbeat": route(0, "id lease-token", "lease-ms registration-sha256"),
132
+ "runtime process admission": route(0, "id lease-token owner state-dir", "daemon-id operation-id operation native-session-id prompt-sha256"),
133
+ "runtime process release-turn": route(0, "id lease-token owner daemon-id", "operation-id observation-file"),
134
+ "runtime process finish": route(0, "id lease-token state", "reason", "", "", { state: ["stopped", "failed"] }),
135
+ "runtime process status": route(0, "", "id lease-token registration-sha256"),
136
+ "runtime process reconcile": route(0, "owner", "grace-ms"),
137
+ "lane status": route(0, project, "lane"),
138
+ "lane waiters": route(0, project, "lane"),
139
+ "lane waiter cancel": route(0, `${project} lane ${worker} reason`),
140
+ "lane workspace set": route(0, `${project} lane path`, "branch"),
141
+ "lane workspace check": route(0, `${project} lane path`),
142
+ "lane lock acquire": route(0, `${project} lane ${worker} reason`, "path ttl"),
143
+ "lane lock heartbeat": route(0, `${project} lane ${worker}`, "path lease-token ttl"),
144
+ "lane lock release": route(0, `${project} lane ${worker} reason`, "path lease-token"),
145
+ "lane lock status": route(0, `${project} lane`),
146
+ "lane lock wait": route(0, `${project} lane ${worker}`, "path timeout poll-interval"),
147
+ "lane lock wait-acquire": route(0, `${project} lane ${worker} reason`, "path timeout poll-interval ttl"),
148
+ "merge-queue status": route(0, "", scoped),
149
+ "merge-queue next": route(0, worker, `${scoped} path`),
150
+ "merge-queue claim": route(1, worker, `${scoped} path`),
151
+ "merge-queue wait-next": route(0, worker, `${scoped} path timeout poll-interval acquire-lock`),
152
+ "merge-queue complete": route(1, `${worker} summary`, `${scoped} path`),
153
+ "merge-queue fail": route(1, `${worker} reason requeue`, `${scoped} path`),
154
+ "merge-queue note": route(1, `${worker} summary`, scoped),
155
+ "merge-queue cancel": route(1, "reason", `${scoped} worker-id path`, "force"),
156
+ "merge serve": route(0, "agent-profile", "poll-seconds max-parallel-projects", "once"),
157
+ "merge server status": route(0),
158
+ "merge server stop": route(1),
159
+ "merge apply": route(1, `${project} work`, lifecycle),
160
+ "merge repair": route(1, project, lifecycle),
161
+ "merge request status": route(1, project),
162
+ "merge request route": route(1, `${project} mode reason`, "", "", "", { mode: ["same_session", "server"] }),
163
+ "merge status": route(0, project, "path"),
164
+ "merge bundle status": route(0, project, "path"),
165
+ "merge bundle claim": route(0, `${project} ${worker}`, "path"),
166
+ "merge bundle complete": route(0, `${project} ${worker} summary`, "path"),
167
+ "merge bundle fail": route(0, `${project} ${worker} reason requeue`, "path"),
168
+ "merge one-shot": route(0, `${project} ${worker}`, "path"),
169
+ "merge-worker status": route(0, project),
170
+ "merge-worker start": route(0, `${project} ${worker}`, "path branch"),
171
+ "merge-worker stop": route(0, `${project} reason`, worker),
172
+ "engine install": route(0, "", "", "force"),
173
+ "engine list": route(0),
174
+ "engine info": route(0, "", "package version"),
175
+ "engine resolve": route(0, "", "root project-root"),
176
+ "engine doctor": route(0, "", "root project-root"),
177
+ "engine bind-run": route(1, `${project} engine-version reason`, "package integrity-checksum"),
178
+ "canon register": route(0, "root"),
179
+ "canon status": route(0, "", "root"),
180
+ "canon resolve": route(0, "", "root"),
181
+ "migration impact": route(0, project, "target-version mode"),
182
+ "migration plan": route(0, project, "source-version target-version run backup-path backup-created-at"),
183
+ "migration report": route(0, project, "source-version target-version run backup-path backup-created-at"),
184
+ "migration verify": route(0, "file"),
185
+ "migration apply": route(0, project, "source-version target-version run backup-path backup-created-at file"),
186
+ "storage migrate-writer": route(0, "database backup", "", "offline"),
187
+ "cleanup scan": route(0, project),
188
+ "cleanup apply": route(0, `${project} plan-file reason`, "", "force"),
189
+ "worktree plan": route(0, "protocol-id", scoped),
190
+ "worktree create": route(0, "protocol-id branch base path", scoped),
191
+ "worktree status": route(0, "protocol-id", scoped),
192
+ "worktree bootstrap": route(0, "protocol-id", scoped),
193
+ "worktree close": route(0, "protocol-id mode", scoped, "", "", { mode: ["keep", "remove"] }),
194
+ "codex home plan": route(0, project, "profile source-home target-home"),
195
+ "codex home init": route(0, project, "profile source-home target-home"),
196
+ "codex home status": route(0, project, "profile"),
197
+ "codex home print-env": route(0, project, "profile"),
198
+ "codex home remove": route(0, `${project} mode`, "profile"),
199
+ "codex hooks print": route(0, project, "target profile"),
200
+ "codex hooks status": route(0, project, "target profile"),
201
+ "codex hooks install": route(0, project, "target profile", "yes"),
202
+ "codex hooks remove": route(0, project, "target profile", "yes"),
203
+ "codex hook handle": route(0, "event", scoped),
204
+ "zcode hook handle": route(0, project),
205
+ "zcode invocation prepare": route(0),
206
+ "zcode event handle": route(0, project),
207
+ "zcode usage ingest": route(0, project),
208
+ "grok event handle": route(0, project),
209
+ "grok usage ingest": route(0, project),
210
+ "opencode event handle": route(0, project),
211
+ "opencode usage ingest": route(0, project),
212
+ "droid event handle": route(0, project),
213
+ "droid usage ingest": route(0, project),
214
+ "agy event handle": route(0, project),
215
+ "agy usage ingest": route(0, project),
216
+ "stat usage": route(0, `${project} run`, "group-by session stage"),
217
+ "stat run sessions ls": route(0, `${project} run`),
218
+ "id next": route(0, `${project} type slug`, "", "", "", { type: ["protocol", "prt", "run", "work", "wrk"] }),
219
+ "integration cmux status": route(0, project),
220
+ "dashboard data": route(0, "", "project project-root protocol", "global"),
221
+ "dashboard render": route(0, "", "project project-root protocol output format"),
222
+ "dashboard render-global": route(0, "", "output format"),
223
+ "dashboard open": route(0, "", "project project-root protocol viewer format"),
224
+ "dashboard refresh": route(0, "", "project project-root protocol open format output", "all"),
225
+ "dashboard refresh-global": route(0, "", "output format"),
226
+ "schema validate": { ...route(0, "schema file", "schema-dir project-root run"), contextualPaths: words("project-root file") },
227
+ "memory permissions preflight": route(0, "root memory-bank flow mode", "tasks")
228
+ };
229
+ export function contextualPathOptionsForLifecycle(operation) {
230
+ const key = lifecycleRouteForOperation(operation);
231
+ return new Set(commandInputs[key]?.contextualPaths ?? []);
232
+ }
233
+ const flag = "__dd_flow_flag__";
234
+ const positiveInteger = new Set(words("limit lease-ms pid owner-pid process-group-id ready-timeout-ms max-parallel-projects expected-revision"));
235
+ const nonnegativeInteger = new Set(words("after generation wait-ms grace-ms available-slots"));
236
+ const positiveNumber = new Set(words("ttl poll-interval poll-seconds"));
237
+ const booleans = new Set(words("write close-sessions cancel-queue release-locks user-required requeue acquire-lock"));
238
+ export function prepareCommandRoute(args) {
239
+ const parsed = parseCommandArgs(args);
240
+ const key = Object.keys(commandInputs).filter(key => key.split(" ").every((part, index) => parsed.positional[index] === part))
241
+ .sort((a, b) => b.split(" ").length - a.split(" ").length)[0];
242
+ const reject = (message, parameter) => { throw new AppError("usage", message, 2, { phase: "prepare", effect: "no_effect", recoverable: true, ...(parameter ? { parameter } : {}) }); };
243
+ if (!key)
244
+ return reject("Unknown command; use dd-flow --help");
245
+ const contract = commandInputs[key];
246
+ if (contract.retired)
247
+ reject(contract.retired);
248
+ const prefixLength = key.split(" ").length;
249
+ const positionCount = parsed.positional.length - prefixLength;
250
+ if (positionCount < contract.positions[0] || positionCount > contract.positions[1])
251
+ reject(`${key} expects ${contract.positions[0] === contract.positions[1] ? contract.positions[0] : `${contract.positions[0]}..${contract.positions[1]}`} positional argument(s)`);
252
+ for (const [name, values] of parsed.options) {
253
+ if (key.startsWith("merge-queue ") && name === "session-id")
254
+ reject("--session-id is no longer accepted on merge-queue commands; use --worker-id", name);
255
+ const upgrade = ["compatibility-mode", "mb-upgrade-mode"].includes(name);
256
+ if (upgrade && !isUpgradeAllowlisted(args))
257
+ reject(`${key} does not support explicit upgrade mode`, name);
258
+ if (!upgrade && !contract.values.includes(name) && !contract.switches.includes(name))
259
+ reject(`${key} does not accept --${name}`, name);
260
+ if (values.length > 1 && !contract.repeatable.includes(name))
261
+ reject(`--${name} must occur once`, name);
262
+ for (const value of values) {
263
+ if (contract.switches.includes(name) || name === "mb-upgrade-mode") {
264
+ if (value !== flag)
265
+ reject(`--${name} is a switch and does not take a value`, name);
266
+ continue;
267
+ }
268
+ if (value === flag || !value.trim())
269
+ reject(`--${name} requires a non-empty value`, name);
270
+ if (name === "compatibility-mode" && value !== "mb-upgrade")
271
+ reject("--compatibility-mode must be mb-upgrade", name);
272
+ const choices = contract.enums[name];
273
+ if (choices && !choices.includes(key === "id next" && name === "type" ? value.toLowerCase() : value))
274
+ reject(`--${name} must be ${choices.join(" or ")}`, name);
275
+ if (booleans.has(name) && !["true", "false"].includes(value))
276
+ reject(`--${name} must be true or false`, name);
277
+ const number = Number(value);
278
+ if (positiveInteger.has(name) && (!/^[0-9]+$/.test(value) || !Number.isSafeInteger(number) || number <= 0)
279
+ || nonnegativeInteger.has(name) && (!/^[0-9]+$/.test(value) || !Number.isSafeInteger(number) || number < 0)
280
+ || positiveNumber.has(name) && (!Number.isFinite(number) || number <= 0)
281
+ || name === "timeout" && (!Number.isFinite(number) || number < 0)
282
+ || name === "available-slots" && number > 15)
283
+ reject(`Invalid numeric value for --${name}`, name);
284
+ }
285
+ }
286
+ for (const name of contract.required)
287
+ if (!parsed.options.has(name))
288
+ reject(`Missing required option: --${name}`, name);
289
+ for (const aliases of [
290
+ ["root", "project-root"], ["payload-file", "json-file"],
291
+ ["payload-file", "payload-json", "payload-base64"], ["semantic-file", "data", "result-file", "result-stdin"],
292
+ ["stage-report", "dashboard"], ["task-file", "plan-item"], ["project", "project-root"],
293
+ ["intake-file", "intake-stdin", "context-file"], ["question-file", "question-stdin"], ["answer-file", "answer-stdin"],
294
+ ["summary-file", "summary-stdin"], ["task-file", "task-stdin"], ["reason", "reason-file"]
295
+ ]) {
296
+ const present = aliases.filter(name => parsed.options.has(name));
297
+ if (present.length > 1)
298
+ reject(`Use only one of ${present.map(name => `--${name}`).join(", ")}`);
299
+ }
300
+ if (contract.values.includes("retry-check") && parsed.options.has("retry-check") !== (parsed.options.has("reason") || parsed.options.has("reason-file")))
301
+ reject("--retry-check requires exactly one of --reason or --reason-file");
302
+ // Retain the dispatcher's historic offsets for nested action positions.
303
+ const family = parsed.positional[0];
304
+ const singleWord = ["status", "version", "transition"].includes(family);
305
+ return { key, family, command: singleWord ? undefined : parsed.positional[1], parsed: { options: parsed.options, positional: parsed.positional.slice(singleWord ? 1 : 2) } };
306
+ }