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

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 +666 -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 +1108 -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 +13 -0
  62. package/dist/harness-runtime/lib/native-children.mjs +78 -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 +119 -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 +747 -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 +1774 -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 +177 -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 +1038 -192
  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 +781 -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
@@ -0,0 +1,272 @@
1
+ import path from "node:path";
2
+ import { createRequire } from "node:module";
3
+ import { parse, quote } from "shell-quote";
4
+ import { lifecycleOperationForArgv } from "./lifecycle-contract.mjs";
5
+
6
+ // One grammar owns structure. shell-quote decodes identified command words;
7
+ // neither parser grants authority or executes expansions.
8
+ const require = createRequire(import.meta.url);
9
+ let parser;
10
+ function shellParser() {
11
+ if (!parser) {
12
+ const Parser = require("tree-sitter"), Bash = require("tree-sitter-bash");
13
+ const initialized = new Parser();
14
+ initialized.setLanguage(Bash);
15
+ parser = initialized;
16
+ }
17
+ return parser;
18
+ }
19
+ const shells = new Set(["bash", "sh", "zsh"]);
20
+ const repeatedContexts = new Set(["for_statement", "while_statement", "function_definition", "c_style_for_statement"]);
21
+ const dynamicTypes = ["command_substitution", "process_substitution", "arithmetic_expansion", "brace_expression", "extglob_pattern"];
22
+ function words(text) {
23
+ try { const entries = parse(text, name => `$${name}`); return entries.every(entry => typeof entry === "string") ? entries : null; }
24
+ catch { return null; }
25
+ }
26
+ function ancestors(node) {
27
+ const result = [];
28
+ for (let parent = node.parent; parent; parent = parent.parent) result.push(parent.type);
29
+ return result;
30
+ }
31
+ function backgroundContext(node) {
32
+ for (let unit = node; unit.parent; unit = unit.parent) {
33
+ const siblings = unit.parent.children;
34
+ if (siblings[siblings.findIndex(child => child.id === unit.id) + 1]?.type === "&") return true;
35
+ }
36
+ return false;
37
+ }
38
+ function dynamicWord(node) {
39
+ return node.descendantsOfType(dynamicTypes).length > 0
40
+ || node.descendantsOfType(["simple_expansion", "expansion"]).some(expansion => !["$DD_FLOW_BIN", "${DD_FLOW_BIN}"].includes(expansion.text))
41
+ || node.type === "word" && /[*?[]/.test(node.text);
42
+ }
43
+ function launcherIndex(values, env) {
44
+ let index = 0;
45
+ while (values[index] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(values[index])) {
46
+ const value = values[index++], equals = value.indexOf("="); env[value.slice(0, equals)] = value.slice(equals + 1);
47
+ }
48
+ if (path.basename(values[index] ?? "") === "env") {
49
+ index++; if (values[index] === "--") index++;
50
+ if (values[index]?.startsWith("-")) return null;
51
+ while (values[index] && /^[A-Za-z_][A-Za-z0-9_]*=/.test(values[index])) {
52
+ const value = values[index++], equals = value.indexOf("="); env[value.slice(0, equals)] = value.slice(equals + 1);
53
+ }
54
+ }
55
+ if (["command", "exec"].includes(values[index] ?? "")) { index++; if (values[index] === "--") index++; }
56
+ return index;
57
+ }
58
+ function executionCwd(node, nodes) {
59
+ const lineage = unit => { const result = []; for (; unit; unit = unit.parent) result.push(unit); return result; };
60
+ const target = lineage(node), targetIds = new Set(target.map(unit => unit.id));
61
+ let cwd;
62
+ for (const previous of nodes) {
63
+ if (previous.endIndex >= node.startIndex) continue;
64
+ const values = words(previous.text);
65
+ if (!values) continue;
66
+ let index = launcherIndex(values, {});
67
+ if (values[index - 1] === "command" && values[index] === "-p") { index++; if (values[index] === "--") index++; }
68
+ if (values[index] === "builtin") { index++; if (values[index] === "--") index++; }
69
+ if (!["cd", "pushd", "popd"].includes(values[index])) continue;
70
+ const directoryIndex = index + (values[index + 1] === "--" ? 2 : 1);
71
+ const source = lineage(previous);
72
+ // A different subshell/substitution cannot change this leaf's directory.
73
+ // Pipelines are not treated as isolated: bash lastpipe is configurable.
74
+ if (source.some(unit => ["subshell", "command_substitution", "process_substitution"].includes(unit.type) && !targetIds.has(unit.id))) continue;
75
+ const common = source.find(unit => targetIds.has(unit.id));
76
+ const andList = unit => unit.type === "list" && unit.children.some(child => child.type === "&&");
77
+ const sourcePath = source.slice(1, source.indexOf(common));
78
+ const targetPath = target.slice(1, target.findIndex(unit => unit.id === common?.id));
79
+ // Only a successful, foreground absolute cd dominating the leaf is a
80
+ // route. This walks the grammar's nested left-associative && lists;
81
+ // it does not evaluate conditionals, directory stacks or shell programs.
82
+ // `command cd` is a builtin in bash but an external child in zsh;
83
+ // without native dialect proof none of these prefixes proves parent cwd.
84
+ if (values.slice(0, index).some(value => ["env", "exec", "command"].includes(path.basename(value)))
85
+ || values[index] !== "cd" || values.length !== directoryIndex + 1 || !path.isAbsolute(values[directoryIndex]) || dynamicWord(previous)
86
+ || backgroundContext(previous) || !common || !andList(common)
87
+ || sourcePath.some(unit => unit.type !== "redirected_statement" && !andList(unit))
88
+ || targetPath.some(unit => !["redirected_statement", "subshell", "compound_statement"].includes(unit.type) && !andList(unit))) return { unresolved: true };
89
+ cwd = path.resolve(values[directoryIndex]);
90
+ }
91
+ return { cwd };
92
+ }
93
+ function invocationFromWords(values, node, wrapped, env) {
94
+ let index = launcherIndex(values, env);
95
+ if (index === null) return null;
96
+ let executable = values[index], launcher;
97
+ if (path.basename(executable ?? "") === "node" && (path.basename(values[index + 1] ?? "") === "cli.js" || values[index + 1] === "$DD_FLOW_BIN")) {
98
+ launcher = executable; executable = values[++index];
99
+ }
100
+ if (!executable || !(path.basename(executable) === "dd-flow" || executable === "$DD_FLOW_BIN" || launcher && path.basename(executable) === "cli.js")) return null;
101
+ const argv = values.slice(index + 1), operation = lifecycleOperationForArgv(argv);
102
+ if (!operation) return null;
103
+ const contexts = ancestors(node);
104
+ const heredoc = node.parent?.type === "redirected_statement" && node.parent.descendantsOfType("heredoc_redirect").length > 0;
105
+ return { operation, executable, ...(launcher ? { launcher } : {}), argv, env, args: parseCommandArgs(argv.slice(operation === "recovery_accept" ? 3 : 2)),
106
+ command: quote(values), rewriteSafe: !wrapped, wrapped, heredoc, contexts, ...(!wrapped ? { sourceRange: { start: node.startIndex, end: node.endIndex } } : {}) };
107
+ }
108
+ export function parseLifecycleCommand(command) {
109
+ const result = analyze(command, false, 0);
110
+ return { ...result, participation: result.kind !== "none" ? "lifecycle" : result.participation ?? (result.auxiliary ? "auxiliary" : result.unresolved ? "unproven" : "none") };
111
+ }
112
+ function auxiliaryFromWords(values) {
113
+ const env = {}, index = launcherIndex(values, env);
114
+ let start = index;
115
+ if (path.basename(values[start] ?? "") === "node" && (path.basename(values[start + 1] ?? "") === "cli.js" || values[start + 1] === "$DD_FLOW_BIN")) start++;
116
+ if (!(path.basename(values[start] ?? "") === "dd-flow" || values[start] === "$DD_FLOW_BIN" || start !== index && path.basename(values[start] ?? "") === "cli.js")) return null;
117
+ const args = parseCommandArgs(values.slice(start + 1)), route = args.positional.slice(0, 3).join(" ");
118
+ const operation = route === "work repair add" ? "work_repair_add" : args.positional[0] === "stage" && args.positional[1] === "block" ? "stage_block" : null;
119
+ return operation ? { operation, args } : null;
120
+ }
121
+ // Recognition is not admission: recovered commands never provide argv authority.
122
+ function recognizedParticipation(root, depth) {
123
+ for (const node of root.descendantsOfType("command")) {
124
+ const name = node.childForFieldName("name");
125
+ if (!name || name.type === "ERROR" || dynamicWord(name)) continue;
126
+ const values = words(node.text);
127
+ if (!values) continue;
128
+ if (invocationFromWords(values, node, true, {})) return "lifecycle";
129
+ if (auxiliaryFromWords(values)) return "auxiliary";
130
+ const index = launcherIndex(values, {});
131
+ if (values[index] === "eval" && depth < 8) {
132
+ const payload = node.namedChildren[index + 1];
133
+ if (payload && !dynamicWord(payload)) {
134
+ const nested = analyze(values.slice(index + 1).join(" "), true, depth + 1);
135
+ if (nested.kind !== "none" || nested.participation === "lifecycle") return "lifecycle";
136
+ if (nested.auxiliary || nested.participation === "auxiliary") return "auxiliary";
137
+ }
138
+ }
139
+ }
140
+ return "unproven";
141
+ }
142
+ function analyze(command, wrapped, depth) {
143
+ if (depth > 8 || command.length > 1_000_000) return { kind: "none", unresolved: true, reason: "shell_input_limit" };
144
+ const tree = shellParser().parse(command);
145
+ if (!tree) return { kind: "none", unresolved: true, reason: "shell_parse_error" };
146
+ try {
147
+ const root = tree.rootNode;
148
+ if (root.hasError) return { kind: "none", unresolved: true, participation: recognizedParticipation(root, depth), reason: "shell_parse_error" };
149
+ const nodes = root.descendantsOfType("command"), found = [], auxiliaries = [];
150
+ let unsupported;
151
+ for (const node of nodes) {
152
+ const name = node.childForFieldName("name"), values = words(node.text);
153
+ if (!name || !values) { if (name && dynamicWord(name)) unsupported ??= "dynamic_executable"; continue; }
154
+ const inherited = {};
155
+ // Conditional exports cannot become unconditional facts in a sibling.
156
+ for (const previous of root.namedChildren) {
157
+ if (previous.endIndex > node.startIndex) break;
158
+ if (previous.type !== "declaration_command" || !previous.text.startsWith("export ")) continue;
159
+ for (const assignment of previous.descendantsOfType("variable_assignment")) {
160
+ if (dynamicWord(assignment)) continue;
161
+ const decoded = words(assignment.text)?.[0], equals = decoded?.indexOf("=") ?? -1;
162
+ if (decoded && equals > 0) inherited[decoded.slice(0, equals)] = decoded.slice(equals + 1);
163
+ }
164
+ }
165
+ const direct = invocationFromWords(values, node, wrapped, inherited);
166
+ const auxiliary = auxiliaryFromWords(values);
167
+ if (auxiliary) {
168
+ const route = executionCwd(node, nodes);
169
+ if (dynamicWord(node) || backgroundContext(node) || ancestors(node).some(type => repeatedContexts.has(type) || ["command_substitution", "process_substitution"].includes(type)) || route.unresolved) unsupported ??= "unsupported_auxiliary_execution";
170
+ auxiliaries.push({ ...auxiliary, ...(route.cwd ? { expectedCwd: route.cwd } : {}) });
171
+ continue;
172
+ }
173
+ if (direct) {
174
+ if (backgroundContext(node)) unsupported ??= "background_lifecycle_execution";
175
+ if (node.namedChildren.some(dynamicWord)) unsupported ??= "unresolved_lifecycle_word";
176
+ if (direct.contexts.some(type => repeatedContexts.has(type))) unsupported ??= "repeated_lifecycle_context";
177
+ if (direct.contexts.some(type => ["command_substitution", "process_substitution"].includes(type))) unsupported ??= "executable_substitution";
178
+ const route = executionCwd(node, nodes);
179
+ if (route.unresolved) unsupported ??= "unresolved_execution_cwd";
180
+ else if (route.cwd) direct.expectedCwd = route.cwd;
181
+ found.push(direct); continue;
182
+ }
183
+ const outerEnv = { ...inherited }, shellIndex = launcherIndex(values, outerEnv);
184
+ const wrapper = path.basename(values[shellIndex] ?? "");
185
+ if (["time", "nohup", "timeout"].includes(wrapper)) {
186
+ let nestedIndex = shellIndex + 1;
187
+ if (wrapper === "time" && values[nestedIndex] === "-p") nestedIndex++;
188
+ if (values[nestedIndex] === "--") nestedIndex++;
189
+ if (wrapper === "timeout" && /^\d+(?:\.\d+)?[smhd]?$/.test(values[nestedIndex] ?? "")) nestedIndex++;
190
+ const nested = analyze(quote(values.slice(nestedIndex)), true, depth + 1);
191
+ if (nested.auxiliary || ["lifecycle", "auxiliary"].includes(nested.participation)) return { kind: "none", participation: nested.auxiliary ? "auxiliary" : nested.participation, unresolved: true, reason: "opaque_execution_wrapper" };
192
+ if (nested.kind !== "none") { found.push(...(nested.invocations ?? [nested.invocation])); unsupported ??= "opaque_execution_wrapper"; continue; }
193
+ if (nested.unresolved) unsupported ??= nested.reason;
194
+ }
195
+ if (shellIndex !== null && shells.has(path.basename(values[shellIndex] ?? "")) && /^-(?:l?c|cl)$/.test(values[shellIndex + 1] ?? "") && values[shellIndex + 2] !== undefined) {
196
+ const payloadNode = node.namedChildren[shellIndex + 2];
197
+ if (!payloadNode || dynamicWord(payloadNode)) { unsupported ??= "dynamic_shell_payload"; continue; }
198
+ if (node.namedChildren.some(child => child !== payloadNode && dynamicWord(child))) unsupported ??= "unresolved_shell_environment";
199
+ const nested = analyze(values[shellIndex + 2], true, depth + 1);
200
+ if (nested.auxiliary) {
201
+ const route = executionCwd(node, nodes);
202
+ if (route.unresolved || backgroundContext(node) || ancestors(node).some(type => repeatedContexts.has(type) || ["command_substitution", "process_substitution"].includes(type))) unsupported ??= "unsupported_auxiliary_execution";
203
+ auxiliaries.push({ ...nested.auxiliary, ...(route.cwd && !nested.auxiliary.expectedCwd ? { expectedCwd: route.cwd } : {}) });
204
+ }
205
+ if (nested.participation && ["lifecycle", "auxiliary"].includes(nested.participation) && nested.unresolved) return { kind: "none", unresolved: true, participation: nested.participation, reason: nested.reason };
206
+ if (nested.kind !== "none") {
207
+ const route = executionCwd(node, nodes);
208
+ if (route.unresolved) unsupported ??= "unresolved_execution_cwd";
209
+ for (const invocation of nested.invocations ?? [nested.invocation]) {
210
+ invocation.env = { ...outerEnv, ...invocation.env };
211
+ if (!invocation.expectedCwd && route.cwd) invocation.expectedCwd = route.cwd;
212
+ }
213
+ found.push(...(nested.invocations ?? [nested.invocation]));
214
+ if (backgroundContext(node)) unsupported ??= "background_lifecycle_execution";
215
+ if (ancestors(node).some(type => repeatedContexts.has(type))) unsupported ??= "repeated_lifecycle_context";
216
+ if (ancestors(node).some(type => ["command_substitution", "process_substitution"].includes(type))) unsupported ??= "executable_substitution";
217
+ if (nested.supported === false) unsupported ??= nested.reason ?? "unsupported_wrapper";
218
+ }
219
+ else if (nested.unresolved) unsupported ??= nested.reason;
220
+ } else if (values[shellIndex] === "eval") {
221
+ const participation = recognizedParticipation(root, depth);
222
+ if (participation !== "unproven") return { kind: "none", unresolved: true, participation, reason: "dynamic_eval" };
223
+ unsupported ??= "dynamic_eval";
224
+ }
225
+ else if (values[shellIndex] === "coproc") unsupported ??= "background_lifecycle_execution";
226
+ else if (dynamicWord(name)) unsupported ??= "dynamic_executable";
227
+ }
228
+ if (auxiliaries.length) {
229
+ if (found.length || auxiliaries.length !== 1) unsupported ??= "multiple_lifecycle_invocations";
230
+ if (!found.length) return { kind: "none", participation: "auxiliary", ...(unsupported ? { unresolved: true, reason: unsupported } : { auxiliary: auxiliaries[0] }) };
231
+ }
232
+ if (!found.length) return unsupported ? { kind: "none", unresolved: true, reason: unsupported } : { kind: "none" };
233
+ if (found.length > 1) unsupported ??= "multiple_lifecycle_invocations";
234
+ const invocation = found[0];
235
+ const composed = nodes.length > 1 || root.namedChildren.length !== 1 || invocation.contexts.some(type => !["program", "redirected_statement", "command"].includes(type));
236
+ return composed || unsupported ? { kind: "compound", invocation, invocations: found, supported: !unsupported, reason: unsupported ?? "shell_composition" }
237
+ : { kind: "standalone", invocation, invocations: found, supported: true };
238
+ } finally { /* Native Tree lifetime is managed by the Node binding. */ }
239
+ }
240
+ export function lifecycleCommandFromPayload(payload) {
241
+ const input = payload.tool_input && typeof payload.tool_input === "object" ? payload.tool_input : {};
242
+ const command = [payload.command, input.command, input.cmd].find(value => typeof value === "string" && value.length > 0);
243
+ return typeof command === "string" ? unwrapShellCommand(command) : undefined;
244
+ }
245
+ export function continuationCommand(command) {
246
+ return parseLifecycleCommand(command).auxiliary ?? null;
247
+ }
248
+ export function unwrapShellCommand(command) {
249
+ const values = words(command); if (!values || values.length !== 3 || !shells.has(path.basename(values[0])) || !/^-(?:l?c|cl)$/.test(values[1])) return command;
250
+ const tree = shellParser().parse(command);
251
+ try { const node = tree?.rootNode.namedChildren[0]?.namedChildren[2]; return node && !dynamicWord(node) ? values[2] : command; }
252
+ finally { /* Native Tree lifetime is managed by the Node binding. */ }
253
+ }
254
+ export function parseCommandArgs(argv) {
255
+ const positional = [], options = new Map();
256
+ for (let index = 0; index < argv.length; index++) {
257
+ const value = argv[index];
258
+ if (value?.startsWith("--")) {
259
+ const equals = value.indexOf("=");
260
+ if (equals > 2) { const key = value.slice(2, equals); options.set(key, [...(options.get(key) ?? []), value.slice(equals + 1)]); continue; }
261
+ const key = value.slice(2), next = argv[index + 1], optionValue = !next || next.startsWith("--") ? "__dd_flow_flag__" : next;
262
+ options.set(key, [...(options.get(key) ?? []), optionValue]); if (optionValue !== "__dd_flow_flag__") index++;
263
+ } else if (value) positional.push(value);
264
+ }
265
+ return { positional, options };
266
+ }
267
+ export function commandOption(invocation, name) { const value = invocation.args.options.get(name)?.at(-1); return value === "__dd_flow_flag__" ? undefined : value; }
268
+ export function commandHasOption(invocation, name) { return invocation.args.options.has(name); }
269
+ export function commandPosition(invocation, index) { return invocation.args.positional[index]; }
270
+ export function shellSuffixOffset(command) {
271
+ const analysis = parseLifecycleCommand(command); return analysis.kind === "none" ? command.length : analysis.invocation.sourceRange?.end ?? command.length;
272
+ }
@@ -0,0 +1,10 @@
1
+ export const lifecycleRoutes: {
2
+ readonly "session register": "session_register"; readonly "stage start": "stage_start"; readonly "stage finish": "stage_finish";
3
+ readonly "stage pause": "stage_pause"; readonly "stage resume": "stage_resume"; readonly "work start": "work_start";
4
+ readonly "work finish": "work_finish"; readonly "work fail": "work_fail"; readonly "merge apply": "merge_apply";
5
+ readonly "merge repair": "merge_repair"; readonly "run recovery accept": "recovery_accept";
6
+ };
7
+ export type LifecycleOperation = typeof lifecycleRoutes[keyof typeof lifecycleRoutes];
8
+ export const lifecycleInternalOptions: readonly ["invocation-id", "hook-event-id"];
9
+ export function lifecycleOperationForArgv(argv: readonly string[]): LifecycleOperation | null;
10
+ export function lifecycleRouteForOperation(operation: LifecycleOperation): keyof typeof lifecycleRoutes;
@@ -0,0 +1,19 @@
1
+ export const lifecycleRoutes = {
2
+ "session register": "session_register", "stage start": "stage_start", "stage finish": "stage_finish",
3
+ "stage pause": "stage_pause", "stage resume": "stage_resume", "work start": "work_start",
4
+ "work finish": "work_finish", "work fail": "work_fail", "merge apply": "merge_apply",
5
+ "merge repair": "merge_repair", "run recovery accept": "recovery_accept"
6
+ };
7
+ export const lifecycleInternalOptions = ["invocation-id", "hook-event-id"];
8
+ export function lifecycleOperationForArgv(argv) {
9
+ if (argv.some(arg => arg === "--help" || arg === "-h")) return null;
10
+ for (const [route, operation] of Object.entries(lifecycleRoutes)) {
11
+ if (route.split(" ").every((part, index) => argv[index] === part)) return operation;
12
+ }
13
+ return null;
14
+ }
15
+ export function lifecycleRouteForOperation(operation) {
16
+ const entry = Object.entries(lifecycleRoutes).find(([, value]) => value === operation);
17
+ if (!entry) throw new Error(`Unknown lifecycle operation: ${operation}`);
18
+ return entry[0];
19
+ }
@@ -0,0 +1,12 @@
1
+ import type { ChildProcess } from "node:child_process";
2
+ type RuntimeConfig = Record<string, unknown>;
3
+ type ProcessBinding = { id: string; lease_token: string; kind?: string; lease_expires_at?: string; registration_sha256?: string };
4
+ export function runtimeProcess(config: RuntimeConfig, action: string, options?: Record<string, unknown>, transport?: { timeoutMs?: number; signal?: AbortSignal }): Promise<Record<string, unknown> | null>;
5
+ export function readbackDaemonProcess(config: RuntimeConfig, record: ProcessBinding, transport?: { signal?: AbortSignal }): Promise<Record<string, unknown>>;
6
+ export function registerDaemonProcess(config: RuntimeConfig, input: { kind: string; [key: string]: unknown }): Promise<ProcessBinding | null>;
7
+ export function confirmDaemonProcess(config: RuntimeConfig, record: ProcessBinding | null | undefined, child: Pick<ChildProcess, "pid"> | null | undefined): Promise<void>;
8
+ export function assertDaemonOwnership(stateDir: string, dispatch?: Record<string, unknown>, assertCurrent?: () => void): Promise<void>;
9
+ export function heartbeatDaemonProcess(config: RuntimeConfig, record: ProcessBinding | null | undefined): Promise<void>;
10
+ export function finishDaemonProcess(config: RuntimeConfig, record: ProcessBinding | null | undefined, state?: string, reason?: string): Promise<void>;
11
+ export function completeBudgetOperation(stateDir: string, request: { id: string; operation: string; params?: { sessionId?: string } }, result: unknown, observe?: () => Promise<Record<string, unknown>>): Promise<Record<string, unknown>>;
12
+ export function stopProcessGroup(child: ChildProcess | null | undefined, graceMs?: number, options?: { detached?: boolean; settlementMs?: number }): Promise<void>;