@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.156

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 (227) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/build-info.json +8 -8
  3. package/dist/cli/command-inputs.js +33 -3
  4. package/dist/cli/command-metadata.js +73 -2
  5. package/dist/cli/delivery.js +108 -0
  6. package/dist/cli/diagnostic-invocation.js +23 -7
  7. package/dist/cli/diagnostic.js +14 -3
  8. package/dist/cli/help.js +117 -1
  9. package/dist/cli/hook-ingress.js +36 -49
  10. package/dist/cli/interactive-invocation.js +33 -0
  11. package/dist/cli/interactive.js +114 -0
  12. package/dist/cli/memory-fix-invocation.js +52 -0
  13. package/dist/cli/memory-fix.js +100 -0
  14. package/dist/cli/memory-flow-admission.js +7 -3
  15. package/dist/cli/memory-init.js +5 -4
  16. package/dist/cli/native-hook-ingress.js +5 -1
  17. package/dist/cli/native-hook-preparation.js +31 -4
  18. package/dist/cli/run-cli.js +274 -19
  19. package/dist/cli.js +17 -3
  20. package/dist/domain/delivery-flows.js +8 -0
  21. package/dist/domain/distill-flow.js +20 -0
  22. package/dist/domain/interactive-flow.js +1 -0
  23. package/dist/domain/memory-fix-flow.js +2 -0
  24. package/dist/domain/review-fix-flow.js +2 -0
  25. package/dist/domain/source-snapshot-flows.js +4 -0
  26. package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
  27. package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
  28. package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
  29. package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
  30. package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
  31. package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
  32. package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
  33. package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
  34. package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
  35. package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
  36. package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
  37. package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
  38. package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
  39. package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
  40. package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
  41. package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
  42. package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
  43. package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
  44. package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
  45. package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
  46. package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +115 -17
  47. package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
  48. package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
  49. package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
  50. package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
  51. package/dist/harness-runtime/lib/delegation-instructions.d.mts +2 -1
  52. package/dist/harness-runtime/lib/delegation-instructions.mjs +15 -5
  53. package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
  54. package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
  55. package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
  56. package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
  57. package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
  58. package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
  59. package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
  60. package/dist/harness-runtime/lib/native-streams.mjs +3 -10
  61. package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
  62. package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
  63. package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
  64. package/dist/harness-runtime/lib/process-json.mjs +4 -5
  65. package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
  66. package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
  67. package/dist/schemas/code-work-result.schema.json +2 -1
  68. package/dist/schemas/delivery-stage-result.schema.json +286 -0
  69. package/dist/schemas/distill-stage-result.schema.json +667 -0
  70. package/dist/schemas/distill-work-result.schema.json +428 -0
  71. package/dist/schemas/execution-bundle.schema.json +6 -2
  72. package/dist/schemas/interactive-stage-result.schema.json +47 -0
  73. package/dist/schemas/interactive-work-result.schema.json +63 -0
  74. package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
  75. package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
  76. package/dist/schemas/review-fix-stage-result.schema.json +210 -0
  77. package/dist/schemas/run-control-receipt.schema.json +1 -0
  78. package/dist/services/assigned-lifecycle.js +382 -0
  79. package/dist/services/check-runner-evidence.js +45 -8
  80. package/dist/services/cli-operation-classifier.js +6 -1
  81. package/dist/services/code-checks.js +46 -29
  82. package/dist/services/codex-hook-delivery.js +11 -2
  83. package/dist/services/controller-child-repair.js +95 -0
  84. package/dist/services/controller-execution.js +4 -1
  85. package/dist/services/controller-fanout.js +19 -9
  86. package/dist/services/controller-memory-worker.js +6 -4
  87. package/dist/services/controller-result-authority.js +122 -0
  88. package/dist/services/dashboard.js +4 -2
  89. package/dist/services/delivery-access.js +123 -0
  90. package/dist/services/delivery-approval.js +68 -0
  91. package/dist/services/delivery-artifact.js +86 -0
  92. package/dist/services/delivery-checks.js +178 -0
  93. package/dist/services/delivery-execution.js +181 -0
  94. package/dist/services/delivery-intents.js +212 -0
  95. package/dist/services/delivery-operation.js +81 -0
  96. package/dist/services/delivery-policy.js +91 -0
  97. package/dist/services/delivery-procedures.js +43 -0
  98. package/dist/services/delivery-request.js +99 -0
  99. package/dist/services/delivery-result.js +144 -0
  100. package/dist/services/delivery-stage-commands.js +26 -0
  101. package/dist/services/delivery-stage-entry.js +56 -0
  102. package/dist/services/delivery-stage-handlers.js +135 -0
  103. package/dist/services/delivery-stage-results.js +94 -0
  104. package/dist/services/descriptor-coordinator-session.js +3 -1
  105. package/dist/services/descriptor-run-start.js +24 -4
  106. package/dist/services/descriptor-stage-coordinator.js +78 -10
  107. package/dist/services/descriptor-stage-settlement.js +59 -14
  108. package/dist/services/diagnostic-repair-intake.js +123 -0
  109. package/dist/services/diagnostic-repair-source-intake.js +102 -0
  110. package/dist/services/diagnostic-report.js +30 -0
  111. package/dist/services/diagnostic-run-bundle.js +48 -9
  112. package/dist/services/diagnostic-snapshot.js +2 -2
  113. package/dist/services/diagnostic-stage-handlers.js +3 -2
  114. package/dist/services/diagnostic-stage-results.js +26 -0
  115. package/dist/services/diagnostic-start.js +56 -6
  116. package/dist/services/diagnostic-work-collection.js +3 -3
  117. package/dist/services/distill-document-publication.js +280 -0
  118. package/dist/services/distill-promotion.js +197 -0
  119. package/dist/services/distill-publication-custody.js +162 -0
  120. package/dist/services/distill-stage-handlers.js +249 -0
  121. package/dist/services/distill-stage-results.js +105 -0
  122. package/dist/services/distill-user-inputs.js +4 -0
  123. package/dist/services/distill-work-batch.js +42 -0
  124. package/dist/services/distill-work-collection.js +74 -0
  125. package/dist/services/distill-work-registration.js +93 -0
  126. package/dist/services/distill-work-results.js +98 -0
  127. package/dist/services/eval-snapshots.js +17 -7
  128. package/dist/services/external-work-launch.js +8 -2
  129. package/dist/services/flow-catalog.js +24 -0
  130. package/dist/services/frozen-engine.js +19 -2
  131. package/dist/services/generic-execution.js +4 -3
  132. package/dist/services/harness-adapter.js +2 -14
  133. package/dist/services/hook-memory-routing.js +1 -1
  134. package/dist/services/hooks.js +31 -6
  135. package/dist/services/hybrid-contracts.js +6 -1
  136. package/dist/services/installed-flow-source.js +37 -2
  137. package/dist/services/interactive-attached-session.js +215 -0
  138. package/dist/services/interactive-baseline-custody.js +54 -0
  139. package/dist/services/interactive-before-versions.js +148 -0
  140. package/dist/services/interactive-bundle-data.js +85 -0
  141. package/dist/services/interactive-checks.js +179 -0
  142. package/dist/services/interactive-execution.js +33 -0
  143. package/dist/services/interactive-finish-integration.js +236 -0
  144. package/dist/services/interactive-git-baseline.js +230 -0
  145. package/dist/services/interactive-git-dispatch.js +278 -0
  146. package/dist/services/interactive-input-delivery.js +82 -0
  147. package/dist/services/interactive-mutation-scope.js +231 -0
  148. package/dist/services/interactive-request.js +287 -0
  149. package/dist/services/interactive-run-bundle.js +109 -0
  150. package/dist/services/interactive-stage-entry.js +41 -0
  151. package/dist/services/interactive-stage-handlers.js +289 -0
  152. package/dist/services/interactive-stage-observation.js +136 -0
  153. package/dist/services/interactive-stage-results.js +111 -0
  154. package/dist/services/interactive-user-inputs.js +142 -0
  155. package/dist/services/interactive-work-batch.js +43 -0
  156. package/dist/services/interactive-work-collection.js +119 -0
  157. package/dist/services/interactive-work-completion.js +180 -0
  158. package/dist/services/interactive-work-context.js +249 -0
  159. package/dist/services/interactive-work-history.js +112 -0
  160. package/dist/services/interactive-work-registration.js +124 -0
  161. package/dist/services/interactive-work-results.js +115 -0
  162. package/dist/services/interactive-work-start.js +155 -0
  163. package/dist/services/lifecycle-invocations.js +250 -33
  164. package/dist/services/managed-daemon-binding.js +52 -1
  165. package/dist/services/memory-compose-handler.js +2 -22
  166. package/dist/services/memory-finish-integration.js +58 -17
  167. package/dist/services/memory-fix-finish-integration.js +129 -0
  168. package/dist/services/memory-fix-git-dispatch.js +139 -0
  169. package/dist/services/memory-fix-intake-custody.js +11 -0
  170. package/dist/services/memory-fix-intake-settlement.js +127 -0
  171. package/dist/services/memory-fix-publication.js +196 -0
  172. package/dist/services/memory-fix-repair-admission.js +116 -0
  173. package/dist/services/memory-fix-request.js +141 -0
  174. package/dist/services/memory-fix-run-bundle.js +83 -0
  175. package/dist/services/memory-fix-scope.js +78 -0
  176. package/dist/services/memory-fix-sources.js +85 -0
  177. package/dist/services/memory-fix-stage-entry.js +24 -0
  178. package/dist/services/memory-fix-stage-handlers.js +282 -0
  179. package/dist/services/memory-fix-stage-results.js +106 -0
  180. package/dist/services/memory-fix-workspace.js +127 -0
  181. package/dist/services/memory-flow-user-inputs.js +88 -0
  182. package/dist/services/memory-init-contexts.js +55 -5
  183. package/dist/services/memory-init-controller-start.js +20 -4
  184. package/dist/services/memory-layer.js +7 -3
  185. package/dist/services/memory-native-review-copy.js +27 -3
  186. package/dist/services/memory-repair-document-delta.js +1 -0
  187. package/dist/services/project-flow-source.js +195 -0
  188. package/dist/services/project-git-integration.js +92 -0
  189. package/dist/services/repair-intents.js +2 -7
  190. package/dist/services/retained-native-hook.js +274 -0
  191. package/dist/services/review-fix-handoff.js +82 -0
  192. package/dist/services/review-fix-intake-custody.js +17 -0
  193. package/dist/services/review-fix-stage-entry.js +43 -0
  194. package/dist/services/review-fix-stage-handlers.js +176 -0
  195. package/dist/services/review-fix-stage-results.js +86 -0
  196. package/dist/services/run-control-worker.js +42 -1
  197. package/dist/services/run-control.js +40 -5
  198. package/dist/services/run-controller-adapter.js +37 -3
  199. package/dist/services/run-controller-process.js +25 -4
  200. package/dist/services/run-controller-recovery.js +46 -6
  201. package/dist/services/run-controller-state.js +3 -2
  202. package/dist/services/run-controller.js +257 -44
  203. package/dist/services/run-recovery.js +31 -2
  204. package/dist/services/run-timeline.js +59 -0
  205. package/dist/services/runs.js +18 -31
  206. package/dist/services/sessions.js +2 -1
  207. package/dist/services/stage-handlers.js +55 -3
  208. package/dist/services/stage-lifecycle.js +4 -3
  209. package/dist/services/stage-pause.js +26 -9
  210. package/dist/services/stage-report-renderer.js +2 -6
  211. package/dist/services/technical-notes.js +31 -0
  212. package/dist/services/verification-matrix.js +1 -1
  213. package/dist/services/version-status.js +1 -1
  214. package/dist/services/vnext-code-review.js +74 -5
  215. package/dist/services/vnext-code.js +8 -32
  216. package/dist/services/vnext-fanout.js +53 -8
  217. package/dist/services/vnext-merge.js +10 -5
  218. package/dist/services/vnext-plan-review.js +1 -1
  219. package/dist/services/vnext-plan.js +1 -1
  220. package/dist/services/vnext-protocolize.js +1 -1
  221. package/dist/services/vnext-specify.js +10 -5
  222. package/dist/services/work-registry.js +255 -69
  223. package/dist/services/worktrees.js +7 -4
  224. package/dist/storage/database.js +2 -1
  225. package/dist/verification-components.json +82 -0
  226. package/package.json +3 -2
  227. package/tools/verification-jobs.mjs +35 -10
package/dist/cli/help.js CHANGED
@@ -2,6 +2,121 @@ import { AppError } from "../shared/errors.js";
2
2
  import { commandInputs, commandAliases } from "./command-inputs.js";
3
3
  import { commandKeyForPositions, globalOptionDocumentation, optionsForCommandTree, routerNativeRoute } from "./command-metadata.js";
4
4
  const helpPages = new Map([
5
+ ...["release", "deploy", "publish"].map((flow) => [`flow ${flow}`, `dd-flow flow ${flow} - start or replay one request-bound delivery RUN
6
+
7
+ Usage:
8
+ dd-flow flow ${flow} --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt --profile ID --json
9
+ dd-flow flow ${flow} --project-root /absolute/project --request-id ID --detach --json
10
+
11
+ Fresh execution uses the installed canon and selected frozen engine, preserves
12
+ the original request and operates in the assigned checkout. Managed Stage
13
+ Sessions use the ordinary controller; independent verification requires a
14
+ different physical Session, not merely another profile name. Starting the RUN
15
+ does not approve a version, target, publication or deployment. Each productive
16
+ operation needs its exact retained user approval and fresh access readback.
17
+
18
+ Replay the same request ID to restore the original RUN, bundle, workspace and
19
+ profiles. Omit current selectors and intake on replay; supplied values must match.
20
+ --prepare-only creates runtime state but launches no provider Session; --detach
21
+ returns the controller receipt without cancelling it. Unknown productive effects
22
+ require original-intent reconciliation, never a new request or automatic retry.
23
+ Use --option NAME --help for each option's exact contract.`]),
24
+ ["delivery", `dd-flow delivery - operations inside an existing delivery RUN
25
+
26
+ Usage:
27
+ dd-flow delivery inputs RUN --project-root /absolute/project --stage STAGE --json
28
+ dd-flow delivery checks RUN --project-root /absolute/project --stage prepare --result-file /absolute/artifact.json --json
29
+ dd-flow delivery execute RUN --project-root /absolute/project --stage publish --result-file /absolute/approval.json --json
30
+ dd-flow delivery reconcile RUN --project-root /absolute/project --stage publish --json
31
+
32
+ inputs is read-only and grants no authority. checks, execute and reconcile must
33
+ use the exact command issued to the current physical Stage coordinator; an
34
+ ambient scope variable or a Work Session cannot authorize them. They never
35
+ allocate a new RUN. Use each command's --help or --option NAME --help for inputs.
36
+ Checks use existing project policy and runner. Execution requires separate exact
37
+ user approval, fresh access and original intent. Reconcile reads the original
38
+ result without new publication or implicit retry; Stage/RUN finish is separate.`],
39
+ ["flow interactive", `dd-flow flow interactive - start or replay one request-bound interactive RUN
40
+
41
+ Usage:
42
+ dd-flow flow interactive --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
43
+ dd-flow flow interactive --project-root /absolute/project --request-id ID --execution managed --intake-file /absolute/request.txt --detach --json
44
+
45
+ The default attached route requires a unique current trusted observation of this
46
+ exact launch argv, workspace and profile. No automatic managed fallback. Managed
47
+ execution must be selected explicitly and uses the existing RUN-bound controller.
48
+
49
+ The project-local curated pack, engine, profiles, original request and Git baseline
50
+ are frozen before execution. Use an existing registered project/prepared Home and
51
+ assigned checkout; this command does not create a worktree or migrate storage.
52
+ --prepare-only retains runtime state without productive dispatch: not a dry run.
53
+ Replay may omit selectors; supplied values must match original retained intent.
54
+
55
+ Stages: preflight → interactive → consolidation → hardening → review → integration.
56
+ Use interactive inputs/turn/finish/scope within this same RUN. Explicit finish
57
+ closes free-turn intake, not review, Git approval or RUN completion. Hardening and
58
+ independent critics use registered Work and actual checks. Scoped Git integration
59
+ preserves unrelated work and never pushes. Historical RUNs keep their own engine.`],
60
+ ["interactive", `dd-flow interactive - operate on an existing interactive RUN
61
+
62
+ Commands:
63
+ inputs Read original request, turns, HITL answers and current digest.
64
+ turn Retain verbatim user input with stable identity and expected digest.
65
+ finish Explicitly close free-turn intake; no new RUN or automatic approval.
66
+ scope Retain selected mutation scope backed by genuine delivered input.
67
+ git-inputs Inspect original selected workspace/index/HEAD versions read-only.
68
+ register Register the initial hardening/review Work graph, not a provider.
69
+ checks Select and execute current obligations through the common runner.
70
+ correction Register a separate author from the exact negative reviewer Work.
71
+ re-review Register a fresh independent critic after correction and checks.
72
+ integrate Perform/reconcile only the accepted scoped local Git operation.
73
+
74
+ All commands require --project-root and --run. Use a command's --help or
75
+ --option NAME --help for its exact required inputs. They do not allocate a new RUN.
76
+ The top-level finish alias accepts exactly the same inputs as interactive finish.
77
+ Read-only inputs/git-inputs never initialize stores. Write operations require the
78
+ existing prepared store; they do not migrate it. Exact retries preserve original
79
+ custody. Unknown Git effects require reconciliation, not another commit or push.
80
+ Use ordinary run control for cancellation; observer disconnect is not stop.`],
81
+ ["flow review-fix", `dd-flow flow review-fix - agree diagnostic scope and retain a raw handoff
82
+
83
+ Usage:
84
+ dd-flow flow review-fix --project-root /absolute/project --request-id ID --source-run RUN-001-audit --finding FIND-001 --intake-file /absolute/request.txt [--profile ID] --json
85
+
86
+ Stages: intake → scope-review → handoff. Select exact accepted diagnostic findings;
87
+ selection is not approval to implement. The private snapshot is read-only.
88
+ Scope-review uses a physically independent Session and considers the original
89
+ request plus every actual delivered answer. Deferred, declined and needs_scope
90
+ remain distinct from a ready handoff. No code, project documents or Git are changed.
91
+
92
+ CLI retains original finding evidence and raw user inputs, not an agent-authored
93
+ replacement specification. Even ready scope grants no formalization authority:
94
+ only a separate explicit user request may start an ordinary SPECIFY-first SDLC RUN.
95
+
96
+ Profiles, engine, canon, findings and original request are frozen before dispatch.
97
+ Replay may omit selectors; supplied values must match the retained request.
98
+ --prepare-only creates the source snapshot and RUN without controller/provider
99
+ Sessions; it is not a dry run. --detach returns the controller receipt.
100
+ Default execution is managed new_session with external concurrency 1; same_session
101
+ cannot provide independent scope-review and refuses before request allocation.`],
102
+ ["flow mb-distill", `dd-flow flow mb-distill - extract and promote reusable practices
103
+
104
+ Usage:
105
+ dd-flow flow mb-distill --project-root /absolute/project --request-id ID --intake-file /absolute/request.txt [--profile ID] --json
106
+
107
+ Stages: preflight → extract → review → promote → verify → finish.
108
+ Extraction uses a read-only source snapshot, domain/process applicability and
109
+ independent novelty/portability critics. Promotion requires a separately accepted destination
110
+ and exact candidate/document write scope. Source selection never grants shared
111
+ canon write authority. Verification reads original selected documents and actual
112
+ published outputs in an independent physical Session; missing evidence refuses.
113
+
114
+ Profiles, engine, canon and original request are frozen before dispatch. Exact
115
+ replay may omit selectors; supplied selectors must match the retained request.
116
+ --prepare-only retains the snapshot and RUN without launching provider Sessions;
117
+ it is not a dry run. --detach returns the controller receipt. Default execution
118
+ uses fresh stage Sessions and external Work with concurrency 1. Use existing
119
+ run drive status and run control commands to observe or reconcile this RUN.`],
5
120
  ["flow mb-audit", `dd-flow flow mb-audit - read-only Memory Bank audit
6
121
 
7
122
  Usage:
@@ -301,6 +416,7 @@ Core commands:
301
416
  storage migrate-writer Explicitly back up and fence a drained SQLite store.
302
417
  runtime scope fence/status/stop Fence, inspect, or cancel one retained EVAL scope.
303
418
  memory permissions preflight Check Memory Bank write/read permissions.
419
+ memory distill inputs/publish Read retained input or publish an accepted scope in an already bound distill RUN.
304
420
  integration cmux status Inspect optional cmux integration.
305
421
  codex home/hooks/hook Manage Codex homes, hooks, and hook events.
306
422
  zcode event/usage Ingest trusted zcode-acp lifecycle and usage evidence.
@@ -431,7 +547,7 @@ Usage:
431
547
  dd-flow run recovery inspect|begin|seal|capture|resume <RUN-ID|RUN-short-id> --project-root <root> --json
432
548
  dd-flow run snapshot create <RUN-ID|RUN-short-id> (--stage-entry <stage>|--candidate|--incomplete|--recovery-id <RCV-ID>) --project-root <root> --output <directory> --json
433
549
  dd-flow run snapshot restore --snapshot <directory> --project-root <prepared-root> [--recovery-id <RCV-ID>] --json
434
- dd-flow run snapshot bootstrap create --project-root <root> --output <directory> [--flow sdlc|mb-init] --json
550
+ dd-flow run snapshot bootstrap create --project-root <root> --output <directory> [--flow <flow>] --json
435
551
  dd-flow run snapshot bootstrap restore --snapshot <directory> --project-root <prepared-root> --json
436
552
  dd-flow run fork --from <sealed-stage-entry-snapshot> --output <empty-directory> --engine-version <exact-version> --request-id <id> [--integrity-checksum <sha256>] --json
437
553
  dd-flow run timeline <RUN-ID|RUN-short-id> --project-root <root> --json
@@ -1,9 +1,9 @@
1
1
  import { fileURLToPath } from "node:url";
2
2
  import { performance } from "node:perf_hooks";
3
- import { channel } from "node:diagnostics_channel";
4
3
  import { parseCommandArgs, parseLifecycleCommand, lifecycleCommandFromPayload, continuationCommand } from "../services/lifecycle-command.js";
5
4
  import { errorRecord, AppError } from "../shared/errors.js";
6
5
  import { parseJsonObject } from "../shared/json.js";
6
+ import { assignedLifecycleHookCommand, retainedWorkStartHookCommand } from "./native-hook-preparation.js";
7
7
  const workerFile = fileURLToPath(import.meta.url);
8
8
  const ingressBudgetMs = 20_000; // Shared admission budget; host separately allows cold CLI startup/cleanup.
9
9
  const { hookBudget, readNativeHookInput } = await import("../harness-runtime/lib/native-hook-command.mjs");
@@ -22,6 +22,7 @@ export async function runHookIngress(args) {
22
22
  throw new AppError("usage", "--project-root requires a value", 2);
23
23
  if (parsed.options.has("json") && parsed.options.get("json")?.[0] !== "__dd_flow_flag__")
24
24
  throw new AppError("usage", "--json does not take a value", 2);
25
+ const started = performance.now();
25
26
  const budget = hookBudget(ingressBudgetMs, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null);
26
27
  const stdin = await readNativeHookInput(process.stdin, budget);
27
28
  const payload = parseJsonObject(stdin || "{}", "native hook stdin");
@@ -31,72 +32,58 @@ export async function runHookIngress(args) {
31
32
  process.stdout.write("{}\n");
32
33
  return 0;
33
34
  }
34
- const started = performance.now();
35
- const { invokeNativeHook } = await import("../harness-runtime/lib/native-hook-command.mjs");
36
- const result = await invokeNativeHook({ bin: workerFile, args, payload, home: process.env.DD_FLOW_HOME, timeoutMs: budget.remaining("admission") });
37
- const { timing, ...output } = result;
38
- process.stderr.write(`${JSON.stringify({ kind: "hook_ingress", total_ms: Math.round(performance.now() - started), ...timing })}\n`);
39
- process.stdout.write(`${JSON.stringify(output)}\n`);
40
- return 0;
41
- }
42
- catch (error) {
43
- const record = errorRecord(error);
44
- process.stderr.write(`${JSON.stringify({ ok: false, error: record })}\n`);
45
- return record.exitCode;
46
- }
47
- }
48
- // A bounded CLI subprocess, not a detached writer: its successful reply follows COMMIT.
49
- if (process.argv[1] === workerFile) {
50
- const started = performance.now();
51
- const timing = channel("dd-flow.sqlite.ingress");
52
- let previousPhase;
53
- const trace = (value) => {
54
- const record = value;
55
- if (record.phase === previousPhase)
56
- return;
57
- previousPhase = record.phase;
58
- process.stderr.write(`${JSON.stringify({ kind: "native_hook_phase", phase: record.phase, elapsed_ms: Math.round(performance.now() - started), ...(record.remaining_ms !== undefined ? { remaining_ms: record.remaining_ms } : {}) })}\n`);
59
- };
60
- timing.subscribe(trace);
61
- try {
62
- const budget = hookBudget(ingressBudgetMs, process.env.DD_FLOW_HOOK_DEADLINE_AT ? Number(process.env.DD_FLOW_HOOK_DEADLINE_AT) : null);
63
- const phase = (name) => trace({ phase: name, remaining_ms: budget.remaining(name) });
35
+ // Assigned Stage and settlement commands are admitted by the CLI's
36
+ // retained packet/CAS, not by a second live hook delivery. A supplied ID
37
+ // is not authority: the CLI still validates the current assignment, owner,
38
+ // generation and exact arguments before any mutation. Work start and all
39
+ // hook-only/legacy commands still need their native identity receipt.
40
+ if (process.env.DD_FLOW_CODEX_STATE_DIR && assignedLifecycleHookCommand(command, process.env)) {
41
+ process.stdout.write("{}\n");
42
+ return 0;
43
+ }
44
+ // Legacy and bootstrap/native-receipt paths still use their existing
45
+ // identity checks. Run them here rather than launching a second Node
46
+ // process with a second deadline; unknown outcomes must not be reissued.
47
+ const phase = (name) => {
48
+ process.stderr.write(`${JSON.stringify({ kind: "native_hook_phase", phase: name, elapsed_ms: Math.round(performance.now() - started), remaining_ms: budget.remaining(name) })}\n`);
49
+ };
64
50
  phase("input");
65
- const stdin = await readNativeHookInput(process.stdin, budget);
66
- const payload = parseJsonObject(stdin, "native hook stdin");
67
51
  phase("verification");
68
52
  const { verifyCodexHookDelivery } = await import("../services/codex-hook-delivery.js");
69
53
  const proof = await verifyCodexHookDelivery(process.env, payload);
70
- const verified = performance.now();
54
+ if (proof && retainedWorkStartHookCommand(command, proof.nativeHookBinding)) {
55
+ process.stdout.write("{}\n");
56
+ return 0;
57
+ }
71
58
  phase("import");
72
59
  const [{ createContext }, { handleCodexHook }] = await Promise.all([import("../runtime/context.js"), import("../services/hooks.js")]);
73
- phase("imported");
74
- const loaded = performance.now();
75
60
  phase("store_open");
76
61
  const context = createContext(process.env, "hook");
77
- const opened = performance.now();
78
62
  try {
79
63
  if (proof)
80
64
  context.codexHookProof = proof;
81
- const parsed = parseCommandArgs(process.argv.slice(5));
82
- const projectRoot = parsed.options.get("project-root")?.[0];
83
65
  phase("receipt");
84
- const result = handleCodexHook(context, { event: "PreToolUse", ...(projectRoot ? { projectRoot } : {}), stdin });
66
+ const result = handleCodexHook(context, { event, ...(root ? { projectRoot: root } : {}), stdin });
85
67
  phase("committed");
86
- phase("ack");
87
- process.stdout.write(`${JSON.stringify({ ...(result.hookSpecificOutput ? { hookSpecificOutput: result.hookSpecificOutput } : {}), timing: {
88
- verify_ms: Math.round(verified - started), load_ms: Math.round(loaded - verified), open_ms: Math.round(opened - loaded), receipt_ms: Math.round(performance.now() - opened)
89
- } })}\n`);
68
+ // Preserve the legacy public response; managed Codex only consumes the
69
+ // native hook protocol field, not internal receipt metadata.
70
+ const output = process.env.DD_FLOW_CODEX_STATE_DIR
71
+ ? result.hookSpecificOutput ? { hookSpecificOutput: result.hookSpecificOutput } : {}
72
+ : result;
73
+ process.stdout.write(`${JSON.stringify(output)}\n`);
90
74
  }
91
75
  finally {
92
76
  context.db.close?.();
93
77
  }
78
+ return 0;
94
79
  }
95
80
  catch (error) {
96
- process.stderr.write(`${JSON.stringify({ ok: false, error: errorRecord(error) })}\n`);
97
- process.exitCode = 1;
98
- }
99
- finally {
100
- timing.unsubscribe(trace);
81
+ const record = errorRecord(error);
82
+ process.stderr.write(`${JSON.stringify({ ok: false, error: record })}\n`);
83
+ return record.exitCode;
101
84
  }
102
85
  }
86
+ // Compatibility with direct invocations of the formerly nested worker.
87
+ if (process.argv[1] === workerFile) {
88
+ process.exitCode = await runHookIngress(process.argv.slice(2));
89
+ }
@@ -0,0 +1,33 @@
1
+ import path from "node:path";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { interactiveStages } from "../domain/interactive-flow.js";
4
+ import { prepareCommandRoute } from "./command-inputs.js";
5
+ import { prepareFlowExecutionSelectors } from "./diagnostic-invocation.js";
6
+ /** Pure input preparation. Defaults remain omitted for exact retained replay. */
7
+ export function prepareInteractiveInvocation(args) {
8
+ const refuse = (message) => { throw new AppError("usage", message, 2, { phase: "prepare", effect: "no_effect" }); };
9
+ const route = prepareCommandRoute(args);
10
+ if (route.key !== "flow interactive")
11
+ refuse("Use flow interactive");
12
+ const options = route.parsed.options, value = (name) => options.get(name)?.[0];
13
+ const projectRoot = value("project-root"), requestId = value("request-id"), workspace = value("workspace");
14
+ if (!path.isAbsolute(projectRoot) || path.resolve(projectRoot) !== projectRoot
15
+ || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(requestId)
16
+ || workspace !== undefined && (!path.isAbsolute(workspace) || path.resolve(workspace) !== workspace))
17
+ refuse("Interactive needs canonical absolute roots and a bounded request ID");
18
+ if (options.has("detach") && options.has("prepare-only"))
19
+ refuse("--detach and --prepare-only are exclusive");
20
+ const selected = prepareFlowExecutionSelectors(options, interactiveStages);
21
+ if (selected.profileFile !== undefined && !path.isAbsolute(selected.profileFile))
22
+ refuse("--profile-file must be absolute");
23
+ const execution = value("execution");
24
+ if (execution)
25
+ selected.execution.execution = execution;
26
+ const gitRoute = value("git-route"), integrationBranch = value("integration-branch");
27
+ if (Boolean(gitRoute) !== Boolean(integrationBranch))
28
+ refuse("--git-route and --integration-branch must be supplied together");
29
+ const gitPolicy = gitRoute
30
+ ? { route: gitRoute, integrationBranch: integrationBranch } : undefined;
31
+ return { scope: { projectRoot, requestId }, workspace, ...selected, gitPolicy, intakeFile: value("intake-file"),
32
+ detach: options.has("detach"), prepareOnly: options.has("prepare-only"), nonInteractive: options.has("non-interactive") };
33
+ }
@@ -0,0 +1,114 @@
1
+ import { createContext } from "../runtime/context.js";
2
+ import { AppError, errorRecord } from "../shared/errors.js";
3
+ import { resolveDdFlowHome, resolveProjectRoot } from "../storage/paths.js";
4
+ import { readInteractiveRequest, assertInteractiveLaunchReplay, retainInteractiveRequest, startInteractiveRun } from "../services/interactive-request.js";
5
+ import { PreparedProjectFlowSource } from "../services/project-flow-source.js";
6
+ import { readSetupStatus } from "../services/setup.js";
7
+ import { selectMemoryInitInstalledEngine } from "../services/memory-init-engine-release.js";
8
+ import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
9
+ import { onboardExecutor } from "../services/executor-onboarding.js";
10
+ import { prepareInteractiveExecution } from "../services/interactive-execution.js";
11
+ import { PreparedInteractiveGitBaseline } from "../services/interactive-git-baseline.js";
12
+ import { PreparedInteractiveBeforeVersions } from "../services/interactive-before-versions.js";
13
+ import { inspectMemoryGitExecutable } from "../services/memory-init-preflight.js";
14
+ import { loadVnextWorkspacePolicy } from "../services/vnext-workspace-policy.js";
15
+ import { readFlowRunExecutionBundle } from "../services/runs.js";
16
+ import { requireProjectByRoot } from "../services/projects.js";
17
+ import { requireRun } from "../services/run-controller-state.js";
18
+ import { prepareInteractiveContexts, publishInteractiveContexts } from "../services/memory-init-contexts.js";
19
+ import { prepareInteractiveInvocation } from "./interactive-invocation.js";
20
+ import { askExecutorChoice } from "./executor-onboarding.js";
21
+ import { prepareTextFile } from "./input-preparation.js";
22
+ import { parseOutputOptions } from "./output-options.js";
23
+ import { prepareResponseDestination, writeResponseFile } from "./response-file.js";
24
+ import { writePreparationError } from "./error-output.js";
25
+ import { launchAndObserveMemoryFlow } from "./memory-init.js";
26
+ /** One request/bundle/RUN owner. Attached returns candidates for the current
27
+ * trusted Session; managed uses the existing detached controller and observer. */
28
+ export async function runInteractiveCli(args, env = process.env, io = process) {
29
+ let context, effects = false;
30
+ try {
31
+ const output = parseOutputOptions(args), invocation = prepareInteractiveInvocation(output.args), home = resolveDdFlowHome(env);
32
+ if (output.responseFile)
33
+ prepareResponseDestination(output.responseFile);
34
+ if (resolveProjectRoot(invocation.scope.projectRoot) !== invocation.scope.projectRoot
35
+ || invocation.workspace !== undefined && resolveProjectRoot(invocation.workspace) !== invocation.workspace)
36
+ throw new AppError("usage", "Interactive roots must be canonical existing checkouts", 2, { effect: "no_effect" });
37
+ if (invocation.profileFile)
38
+ invocation.execution.rootProfile = readOperatorProfileInput(invocation.profileFile);
39
+ const raw = structuredClone(invocation.execution);
40
+ const requestText = invocation.intakeFile ? prepareTextFile(invocation.intakeFile, "interactive request", 64 * 1024).text : undefined;
41
+ context = createContext(env, "prepared_existing", undefined, { resourceDatabaseMode: "prepared_existing" });
42
+ const retained = readInteractiveRequest(context, invocation.scope);
43
+ if (retained) {
44
+ assertInteractiveLaunchReplay(context, invocation.scope, { launchSelection: raw,
45
+ ...(invocation.workspace !== undefined ? { workspace: invocation.workspace } : {}),
46
+ ...(requestText !== undefined ? { requestText } : {}), ...(invocation.gitPolicy ? { gitPolicy: invocation.gitPolicy } : {}) });
47
+ const originalExecution = retained.blobs.find(item => item.path === "runtime/execution.json");
48
+ if (!originalExecution)
49
+ throw new AppError("interactive_request_invalid", "Restore the original execution selection", 2, { effect: "no_effect" });
50
+ const execution = JSON.parse(Buffer.from(originalExecution.base64, "base64").toString());
51
+ if (invocation.detach && execution.execution !== "managed")
52
+ throw new AppError("usage", "--detach requires managed execution", 2, { effect: "no_effect" });
53
+ }
54
+ else {
55
+ if (!requestText?.trim())
56
+ throw new AppError("usage", "Fresh interactive flow requires --intake-file", 2, { effect: "no_effect" });
57
+ if (invocation.detach && invocation.execution.execution !== "managed")
58
+ throw new AppError("usage", "--detach requires explicit managed execution", 2, { effect: "no_effect" });
59
+ const workspace = invocation.workspace ?? invocation.scope.projectRoot;
60
+ const source = PreparedProjectFlowSource.fromProject({ projectRoot: invocation.scope.projectRoot, workspace, flowId: "interactive" });
61
+ const installation = readSetupStatus(home).installation;
62
+ if (!installation)
63
+ throw new AppError("interactive_setup_required", "Install the desired CLI release before interactive execution", 2, { effect: "no_effect" });
64
+ const engine = selectMemoryInitInstalledEngine(home, installation.manifest_sha256);
65
+ engine.admitInteractiveFlow(source);
66
+ const settings = readOperatorProfiles(home), selectedRoot = invocation.execution.rootProfile, stdin = io.stdin ?? process.stdin;
67
+ const executor = await onboardExecutor(typeof selectedRoot === "string" ? { profileId: selectedRoot } : selectedRoot ? { profile: selectedRoot } : {}, () => settings, { allowSkip: false, nonInteractive: invocation.nonInteractive || output.json || output.progressJsonl
68
+ || !stdin.isTTY || !io.stderr.isTTY }, profiles => askExecutorChoice(profiles, { stdin, stderr: io.stderr }, { allowSave: false, allowSkip: false }));
69
+ if (!executor.profile)
70
+ throw new AppError("interactive_executor_required", "Select an interactive executor", 2, { effect: "no_effect" });
71
+ const execution = prepareInteractiveExecution(context, { scope: { ...invocation.scope, workspace }, flow: source.resolveRegisteredFlow(), profiles: settings,
72
+ selection: { ...raw, rootProfile: selectedRoot ?? executor.profile }, launchArgs: args });
73
+ const gitExecutable = inspectMemoryGitExecutable(env).executable;
74
+ const defaults = invocation.gitPolicy ? null : loadVnextWorkspacePolicy(invocation.scope.projectRoot).workspace;
75
+ const gitPolicy = invocation.gitPolicy ?? { route: defaults.route, integrationBranch: defaults.integration_branch };
76
+ const baseline = PreparedInteractiveGitBaseline.capture({ projectRoot: invocation.scope.projectRoot, workspace, gitExecutable, env });
77
+ const beforeVersions = PreparedInteractiveBeforeVersions.capture(baseline, []);
78
+ effects = true;
79
+ retainInteractiveRequest(context, { source, engine, execution: execution.execution, baseline, beforeVersions,
80
+ boundAt: context.now(), requestText, gitExecutable, gitPolicy, launchSelection: raw, receipts: execution.receipts, receiptBytes: execution.receiptBytes }, execution.attached ?? undefined);
81
+ }
82
+ effects = true;
83
+ const started = startInteractiveRun(context, invocation.scope), bundle = readFlowRunExecutionBundle(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId });
84
+ if (!bundle || bundle.manifest.flow_id !== "interactive")
85
+ throw new AppError("interactive_bundle_missing", "Restore the original interactive RUN bundle", 1, { effect: "committed" });
86
+ const workspaceRoot = bundle.execution.snapshot().scope.workspace;
87
+ if (bundle.execution.settings.execution === "managed")
88
+ return await launchAndObserveMemoryFlow(context, { scope: { ...invocation.scope, workspaceRoot }, prepareOnly: invocation.prepareOnly, detach: invocation.detach }, started, "interactive", output, env, io);
89
+ const run = requireRun(context, requireProjectByRoot(context, invocation.scope.projectRoot).id, started.runId);
90
+ const row = context.db.get("SELECT payload_json FROM audit_events WHERE project_id=? AND event_type='flow_run.start_root_owned' AND json_extract(payload_json,'$.run_id')=?", [run.project_id, run.id]);
91
+ const owner = row ? JSON.parse(row.payload_json).owner : null;
92
+ if (!owner || !Number.isSafeInteger(owner.dev) || !Number.isSafeInteger(owner.ino))
93
+ throw new AppError("interactive_root_owner_missing", "Restore original RUN directory ownership", 1, { effect: "committed" });
94
+ const contexts = prepareInteractiveContexts(bundle, run.run_root);
95
+ publishInteractiveContexts(bundle, contexts.objective, run.run_root, contexts.sha256, owner);
96
+ const entry = bundle.descriptor.descriptor.entry_stage;
97
+ const response = { ok: true, action: invocation.prepareOnly ? "flow_interactive_prepared" : "flow_interactive_attached", flow_id: "interactive",
98
+ ...started, run_id: started.runId, request_id: invocation.scope.requestId, project_root: run.project_root,
99
+ workspace_root: run.workspace_root, run_root: run.run_root, context_file: contexts.file, context_sha256: contexts.sha256,
100
+ entry_stage: entry, entry_context: JSON.parse(contexts.files.get(contexts.file).toString()).stages[entry] };
101
+ io.stdout.write(`${JSON.stringify(writeResponseFile(response, output), null, output.json || output.progressJsonl ? undefined : 2)}\n`);
102
+ return 0;
103
+ }
104
+ catch (error) {
105
+ const record = errorRecord(error);
106
+ if (effects)
107
+ record.details = { ...record.details, effect: ["unknown", "committed", "partial"].includes(String(record.details.effect)) ? record.details.effect : "partial", next_action: "reconcile_exact_request" };
108
+ writePreparationError(io, args, record);
109
+ return record.exitCode;
110
+ }
111
+ finally {
112
+ context?.db.close?.();
113
+ }
114
+ }
@@ -0,0 +1,52 @@
1
+ import path from "node:path";
2
+ import { AppError } from "../shared/errors.js";
3
+ import { memoryFixStages } from "../domain/memory-fix-flow.js";
4
+ import { prepareCommandRoute } from "./command-inputs.js";
5
+ import { prepareFlowExecutionSelectors } from "./diagnostic-invocation.js";
6
+ import { hybridDigest } from "../services/hybrid-contracts.js";
7
+ function refuse(message) { throw new AppError("usage", message, 2, { phase: "prepare", effect: "no_effect" }); }
8
+ /** No file/profile lookup, workspace creation or RUN allocation. Omitted fresh
9
+ * selectors remain omitted so a replay can use its retained choices. */
10
+ export function prepareMemoryFixInvocation(args) {
11
+ const route = prepareCommandRoute(args);
12
+ if (route.key !== "flow mb-fix")
13
+ refuse("Use flow mb-fix");
14
+ const options = route.parsed.options, value = (name) => options.get(name)?.[0];
15
+ const projectRoot = value("project-root"), requestId = value("request-id"), workspaceRoot = value("workspace-root");
16
+ if (!path.isAbsolute(projectRoot) || path.resolve(projectRoot) !== projectRoot
17
+ || !/^[A-Za-z0-9][A-Za-z0-9_.-]{0,95}$/.test(requestId)
18
+ || workspaceRoot !== undefined && (!path.isAbsolute(workspaceRoot) || path.resolve(workspaceRoot) !== workspaceRoot))
19
+ refuse("Repair needs canonical absolute roots and a bounded request ID");
20
+ const sourceRunId = value("source-run"), findingIds = options.get("finding");
21
+ if (sourceRunId !== undefined && !/^RUN-[0-9]+(?:-[a-z0-9][a-z0-9-]*)?$/.test(sourceRunId))
22
+ refuse("--source-run must identify one diagnostic RUN");
23
+ if (Boolean(sourceRunId) !== Boolean(findingIds) || findingIds && (!findingIds.length || findingIds.length > 4096
24
+ || findingIds.some(id => !id.trim() || id.length > 256 || /[\r\n\0]/.test(id)) || new Set(findingIds).size !== findingIds.length))
25
+ refuse("Select one --source-run with unique explicit --finding IDs, or omit both on replay");
26
+ if (options.has("detach") && options.has("prepare-only"))
27
+ refuse("--detach and --prepare-only are exclusive");
28
+ const gitRoute = value("git-route"), integrationBranch = value("integration-branch");
29
+ if (Boolean(gitRoute) !== Boolean(integrationBranch))
30
+ refuse("--git-route and --integration-branch must be supplied together");
31
+ const gitPolicy = gitRoute ? { route: gitRoute, integrationBranch: integrationBranch } : undefined;
32
+ const selected = prepareFlowExecutionSelectors(options, memoryFixStages);
33
+ if (selected.profileFile !== undefined && !path.isAbsolute(selected.profileFile))
34
+ refuse("--profile-file must be absolute");
35
+ return { scope: { projectRoot, requestId }, workspaceRoot, sourceRunId, findingIds: findingIds ? [...findingIds] : undefined,
36
+ ...selected, gitPolicy, intakeFile: value("intake-file"), detach: options.has("detach"), prepareOnly: options.has("prepare-only"),
37
+ nonInteractive: options.has("non-interactive") };
38
+ }
39
+ /** Supplied selectors compare with original raw intent, never current aliases,
40
+ * defaults or an inferred equivalent model. Omitted selectors remain pinned. */
41
+ export function assertMemoryFixInvocationReplay(invocation, original, intakeText) {
42
+ if (hybridDigest(invocation.scope) !== hybridDigest(original.scope)
43
+ || invocation.workspaceRoot !== undefined && invocation.workspaceRoot !== original.execution.scope.workspace
44
+ || invocation.sourceRunId !== undefined && invocation.sourceRunId !== original.source_run_id
45
+ || invocation.findingIds !== undefined && hybridDigest(invocation.findingIds) !== hybridDigest(original.finding_ids)
46
+ || invocation.gitPolicy !== undefined && hybridDigest(invocation.gitPolicy) !== hybridDigest(original.git_policy)
47
+ || invocation.intakeFile !== undefined && intakeText !== original.request_text
48
+ || Object.entries(invocation.execution).some(([key, value]) => !original.launch_selection
49
+ || !Object.hasOwn(original.launch_selection, key)
50
+ || hybridDigest(value) !== hybridDigest(original.launch_selection[key])))
51
+ throw new AppError("memory_fix_request_conflict", "Repair request belongs to different original launch inputs", 2, { effect: "no_effect", next_action: "replay_exact_request_or_choose_new_id" });
52
+ }
@@ -0,0 +1,100 @@
1
+ import { createContext } from "../runtime/context.js";
2
+ import { AppError, errorRecord } from "../shared/errors.js";
3
+ import { resolveDdFlowHome, resolveProjectRoot } from "../storage/paths.js";
4
+ import { readMemoryFixRequest, retainMemoryFixRequest, startMemoryFixRun } from "../services/memory-fix-request.js";
5
+ import { readDiagnosticRepairIntake } from "../services/diagnostic-repair-intake.js";
6
+ import { prepareMemoryFixWorkspace } from "../services/memory-fix-workspace.js";
7
+ import { readSetupStatus } from "../services/setup.js";
8
+ import { PreparedInstalledFlowSource } from "../services/installed-flow-source.js";
9
+ import { selectMemoryInitInstalledEngine } from "../services/memory-init-engine-release.js";
10
+ import { FrozenGenericExecution } from "../services/generic-execution.js";
11
+ import { readOperatorProfiles, readOperatorProfileInput } from "../services/operator-profiles.js";
12
+ import { onboardExecutor } from "../services/executor-onboarding.js";
13
+ import { askExecutorChoice } from "./executor-onboarding.js";
14
+ import { prepareMemoryFixInvocation, assertMemoryFixInvocationReplay } from "./memory-fix-invocation.js";
15
+ import { parseOutputOptions } from "./output-options.js";
16
+ import { prepareTextFile } from "./input-preparation.js";
17
+ import { prepareResponseDestination } from "./response-file.js";
18
+ import { writePreparationError } from "./error-output.js";
19
+ import { launchAndObserveMemoryFlow } from "./memory-init.js";
20
+ import { readFlowRunExecutionBundle } from "../services/runs.js";
21
+ import { hybridDigest } from "../services/hybrid-contracts.js";
22
+ /** Public repair entry uses existing prepared stores: a fresh repair already
23
+ * requires an accepted source diagnostic in this Home. Never initializes a new
24
+ * project/Home, migrates stores or changes global profiles to find that source. */
25
+ export async function runMemoryFixCli(args, env = process.env, io = process) {
26
+ let context, effects = false;
27
+ try {
28
+ const output = parseOutputOptions(args), invocation = prepareMemoryFixInvocation(output.args), home = resolveDdFlowHome(env);
29
+ if (output.responseFile)
30
+ prepareResponseDestination(output.responseFile);
31
+ if (resolveProjectRoot(invocation.scope.projectRoot) !== invocation.scope.projectRoot)
32
+ throw new AppError("usage", "Repair project root must be canonical", 2, { effect: "no_effect" });
33
+ const reader = createContext(env, "read_existing");
34
+ let retained;
35
+ try {
36
+ retained = readMemoryFixRequest(reader, invocation.scope);
37
+ if (!retained) {
38
+ if (!invocation.sourceRunId || !invocation.findingIds)
39
+ throw new AppError("usage", "Fresh repair requires --source-run and explicit --finding IDs", 2, { effect: "no_effect" });
40
+ readDiagnosticRepairIntake(reader, { projectRoot: invocation.scope.projectRoot, sourceRunId: invocation.sourceRunId, findingIds: invocation.findingIds });
41
+ }
42
+ }
43
+ finally {
44
+ reader.db.close?.();
45
+ }
46
+ if (invocation.profileFile)
47
+ invocation.execution.rootProfile = readOperatorProfileInput(invocation.profileFile);
48
+ let objective;
49
+ if (invocation.intakeFile)
50
+ objective = prepareTextFile(invocation.intakeFile, "repair intake", 64 * 1024).text;
51
+ if (retained)
52
+ assertMemoryFixInvocationReplay(invocation, retained, objective);
53
+ else if (!objective?.trim())
54
+ throw new AppError("usage", "Fresh repair requires --intake-file with its original request and constraints", 2, { effect: "no_effect" });
55
+ if (!retained) {
56
+ const installation = readSetupStatus(home).installation;
57
+ if (!installation)
58
+ throw new AppError("memory_fix_setup_required", "Install the desired canon and CLI before repair", 2, { effect: "no_effect" });
59
+ const source = PreparedInstalledFlowSource.fromInstalledRelease(home, installation.manifest_sha256, "mb-fix");
60
+ const engine = selectMemoryInitInstalledEngine(home, installation.manifest_sha256);
61
+ engine.admitMemoryFixFlow(source);
62
+ const settings = readOperatorProfiles(home), selectedRoot = invocation.execution.rootProfile, stdin = io.stdin ?? process.stdin;
63
+ const executor = await onboardExecutor(typeof selectedRoot === "string" ? { profileId: selectedRoot } : selectedRoot ? { profile: selectedRoot } : {}, () => settings, { allowSkip: false, nonInteractive: invocation.nonInteractive || output.json || output.progressJsonl
64
+ || !stdin.isTTY || !io.stderr.isTTY }, profiles => askExecutorChoice(profiles, { stdin, stderr: io.stderr }, { allowSave: false, allowSkip: false }));
65
+ if (!executor.profile)
66
+ throw new AppError("memory_fix_executor_required", "Select a repair executor", 2, { effect: "no_effect" });
67
+ const selection = { ...invocation.execution, rootProfile: selectedRoot ?? executor.profile,
68
+ execution: "managed", stageSessionMode: "new_session", delegation: invocation.execution.delegation ?? { mode: "external", max_parallel: 1 } };
69
+ const preview = FrozenGenericExecution.prepare(source.resolveRegisteredFlow(), settings, selection, { ...invocation.scope, workspace: invocation.workspaceRoot ?? invocation.scope.projectRoot });
70
+ context = createContext(env, "prepared_existing", undefined, { resourceDatabaseMode: "prepared_existing" });
71
+ effects = true;
72
+ const workspace = await prepareMemoryFixWorkspace(context, { ...invocation.scope,
73
+ ...(invocation.workspaceRoot ? { workspaceRoot: invocation.workspaceRoot } : {}), ...(invocation.gitPolicy ? { gitPolicy: invocation.gitPolicy } : {}),
74
+ selectionSha256: hybridDigest({ objective, source_run: invocation.sourceRunId, finding_ids: invocation.findingIds,
75
+ raw: invocation.execution, profiles: preview.snapshot().profiles, settings: preview.settings,
76
+ source_sha256: source.sha256, engine_manifest_sha256: engine.manifestSha256 }) });
77
+ const execution = FrozenGenericExecution.prepare(source.resolveRegisteredFlow(), settings, selection, { ...invocation.scope, workspace: workspace.workspaceRoot });
78
+ retainMemoryFixRequest(context, { source, execution, engine, boundAt: context.now(), requestText: objective, sourceRunId: invocation.sourceRunId,
79
+ findingIds: invocation.findingIds, gitExecutable: workspace.gitExecutable, gitPolicy: workspace.gitPolicy }, invocation.execution);
80
+ }
81
+ else
82
+ context = createContext(env, "prepared_existing", undefined, { resourceDatabaseMode: "prepared_existing" });
83
+ effects = true;
84
+ const started = startMemoryFixRun(context, invocation.scope), bundle = readFlowRunExecutionBundle(context, { projectRoot: invocation.scope.projectRoot, runId: started.runId });
85
+ if (!bundle || bundle.manifest.flow_id !== "mb-fix")
86
+ throw new AppError("memory_fix_bundle_missing", "Restore the retained repair bundle", 1, { effect: "committed" });
87
+ return await launchAndObserveMemoryFlow(context, { scope: { ...invocation.scope, workspaceRoot: bundle.execution.snapshot().scope.workspace },
88
+ prepareOnly: invocation.prepareOnly, detach: invocation.detach }, started, "mb-fix", output, env, io);
89
+ }
90
+ catch (error) {
91
+ const record = errorRecord(error);
92
+ if (effects)
93
+ record.details = { ...record.details, effect: ["unknown", "committed", "partial"].includes(String(record.details.effect)) ? record.details.effect : "partial", next_action: "reconcile_exact_request" };
94
+ writePreparationError(io, args, record);
95
+ return record.exitCode;
96
+ }
97
+ finally {
98
+ context?.db.close?.();
99
+ }
100
+ }
@@ -2,11 +2,13 @@ import { prepareCommandRoute } from "./command-inputs.js";
2
2
  import { parseOutputOptions } from "./output-options.js";
3
3
  import { readBoundedUtf8Stream, prepareJsonText } from "./input-preparation.js";
4
4
  import { PreparedInstalledFlowSource } from "../services/installed-flow-source.js";
5
+ import { PreparedProjectFlowSource } from "../services/project-flow-source.js";
5
6
  import { hybridDigest } from "../services/hybrid-contracts.js";
6
7
  import { AppError, errorRecord } from "../shared/errors.js";
7
8
  import { writePreparationError } from "./error-output.js";
8
9
  import { memoryInitStartupInputSchemas } from "../services/memory-init-startup-custody.js";
9
10
  import { isDiagnosticFlow } from "../domain/diagnostic-flows.js";
11
+ import { isDeliveryFlow } from "../domain/delivery-flows.js";
10
12
  /** Executing engine's real handler admission only. The caller owns independent
11
13
  * release/source selection; this command never opens storage or creates a RUN. */
12
14
  export async function runMemoryFlowAdmissionCli(args, io = process) {
@@ -22,7 +24,7 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
22
24
  }
23
25
  const selected = input;
24
26
  const operation = route.parsed.options.get("operation")?.[0] ?? "";
25
- const flowId = isDiagnosticFlow(operation) ? operation : operation === "memory-upgrade" ? "mb-upgrade" : "mb-init";
27
+ const flowId = isDeliveryFlow(operation) ? operation : operation === "interactive" ? "interactive" : operation === "review-fix" ? "review-fix" : operation === "mb-fix" ? "mb-fix" : operation === "mb-distill" ? "mb-distill" : isDiagnosticFlow(operation) ? operation : operation === "memory-upgrade" ? "mb-upgrade" : "mb-init";
26
28
  const startupSchema = memoryInitStartupInputSchemas.find(schema => schema === selected.startup_input_schema);
27
29
  if (Object.hasOwn(selected, "startup_input_schema") && (startupSchema === undefined || flowId !== "mb-init")) {
28
30
  throw new AppError("usage", "Executing engine does not support the requested startup input schema", 2, { effect: "no_effect" });
@@ -30,11 +32,13 @@ export async function runMemoryFlowAdmissionCli(args, io = process) {
30
32
  if (typeof selected.source_sha256 !== "string" || !/^[a-f0-9]{64}$/.test(selected.source_sha256)) {
31
33
  throw new AppError("usage", "Flow admission requires a SHA-256 source digest", 2, { effect: "no_effect" });
32
34
  }
33
- const source = PreparedInstalledFlowSource.restoreRetained(selected.source, selected.source_sha256);
35
+ const source = flowId === "interactive"
36
+ ? PreparedProjectFlowSource.restoreRetained(selected.source, selected.source_sha256)
37
+ : PreparedInstalledFlowSource.restoreRetained(selected.source, selected.source_sha256);
34
38
  const flow = source.resolveRegisteredFlow();
35
39
  if (flow.descriptor.id !== flowId)
36
40
  throw new AppError("usage", "Flow source does not match the requested engine admission operation", 2, { effect: "no_effect" });
37
- const facts = { schema_id: isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
41
+ const facts = { schema_id: isDeliveryFlow(flowId) ? "dd-flow/delivery-engine-flow@1" : flowId === "interactive" ? "dd-flow/interactive-engine-flow@1" : flowId === "review-fix" ? "dd-flow/review-fix-engine-flow@1" : flowId === "mb-fix" ? "dd-flow/memory-fix-engine-flow@1" : flowId === "mb-distill" ? "dd-flow/distill-engine-flow@1" : isDiagnosticFlow(flowId) ? "dd-flow/diagnostic-engine-flow@1" : flowId === "mb-upgrade" ? "dd-flow/memory-upgrade-engine-flow@1"
38
42
  : startupSchema === undefined ? "dd-flow/memory-init-engine-flow@1" : "dd-flow/memory-init-engine-flow@2",
39
43
  ...(startupSchema === undefined ? {} : { startup_input_schema: startupSchema }),
40
44
  flow_id: flowId, source_sha256: source.sha256,