@deksden-com/dd-flow-cli 0.9.0-beta.152 → 0.9.0-beta.155
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.
- package/CHANGELOG.md +23 -0
- package/dist/build-info.json +8 -8
- package/dist/cli/command-inputs.js +33 -3
- package/dist/cli/command-metadata.js +73 -2
- package/dist/cli/delivery.js +108 -0
- package/dist/cli/diagnostic-invocation.js +23 -7
- package/dist/cli/diagnostic.js +14 -3
- package/dist/cli/help.js +117 -1
- package/dist/cli/hook-ingress.js +36 -49
- package/dist/cli/interactive-invocation.js +33 -0
- package/dist/cli/interactive.js +114 -0
- package/dist/cli/memory-fix-invocation.js +52 -0
- package/dist/cli/memory-fix.js +100 -0
- package/dist/cli/memory-flow-admission.js +7 -3
- package/dist/cli/memory-init.js +5 -4
- package/dist/cli/native-hook-ingress.js +5 -1
- package/dist/cli/native-hook-preparation.js +31 -4
- package/dist/cli/run-cli.js +273 -19
- package/dist/cli.js +17 -3
- package/dist/domain/delivery-flows.js +8 -0
- package/dist/domain/distill-flow.js +20 -0
- package/dist/domain/interactive-flow.js +1 -0
- package/dist/domain/memory-fix-flow.js +2 -0
- package/dist/domain/review-fix-flow.js +2 -0
- package/dist/domain/source-snapshot-flows.js +4 -0
- package/dist/harness-runtime/bin/dd-agy.mjs +18 -3
- package/dist/harness-runtime/bin/dd-grok.mjs +15 -5
- package/dist/harness-runtime/bin/dd-zcode.mjs +15 -7
- package/dist/harness-runtime/lib/agy-error-boundary.mjs +18 -10
- package/dist/harness-runtime/lib/agy-native-inspection.mjs +12 -3
- package/dist/harness-runtime/lib/agy-native-inventory.mjs +19 -0
- package/dist/harness-runtime/lib/agy-terminal-envelope.mjs +13 -6
- package/dist/harness-runtime/lib/agy-transcript-stream.mjs +54 -0
- package/dist/harness-runtime/lib/daemon-observation.d.mts +1 -0
- package/dist/harness-runtime/lib/daemon-observation.mjs +12 -4
- package/dist/harness-runtime/lib/daemon-operations.mjs +20 -5
- package/dist/harness-runtime/lib/dd-agy-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-agy-daemon.mjs +371 -114
- package/dist/harness-runtime/lib/dd-codex-daemon.mjs +43 -3
- package/dist/harness-runtime/lib/dd-codex.mjs +36 -4
- package/dist/harness-runtime/lib/dd-droid-daemon.mjs +25 -11
- package/dist/harness-runtime/lib/dd-droid.mjs +124 -11
- package/dist/harness-runtime/lib/dd-grok-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-grok-daemon.mjs +137 -25
- package/dist/harness-runtime/lib/dd-grok.mjs +4 -2
- package/dist/harness-runtime/lib/dd-opencode-daemon.mjs +114 -16
- package/dist/harness-runtime/lib/dd-opencode.mjs +10 -7
- package/dist/harness-runtime/lib/dd-zcode-daemon.d.mts +1 -0
- package/dist/harness-runtime/lib/dd-zcode-daemon.mjs +115 -22
- package/dist/harness-runtime/lib/dd-zcode.mjs +75 -36
- package/dist/harness-runtime/lib/delegation-instructions.d.mts +1 -0
- package/dist/harness-runtime/lib/delegation-instructions.mjs +10 -3
- package/dist/harness-runtime/lib/delivery-command.mjs +84 -0
- package/dist/harness-runtime/lib/driver-recovery.mjs +0 -19
- package/dist/harness-runtime/lib/droid-observation.mjs +9 -5
- package/dist/harness-runtime/lib/lifecycle-contract.d.mts +1 -0
- package/dist/harness-runtime/lib/lifecycle-contract.mjs +2 -1
- package/dist/harness-runtime/lib/native-hook-command.d.mts +3 -2
- package/dist/harness-runtime/lib/native-hook-command.mjs +63 -8
- package/dist/harness-runtime/lib/native-streams.mjs +3 -10
- package/dist/harness-runtime/lib/opencode-plugin.mjs +36 -9
- package/dist/harness-runtime/lib/operation-errors.mjs +13 -0
- package/dist/harness-runtime/lib/operation-progress.mjs +6 -2
- package/dist/harness-runtime/lib/process-json.mjs +4 -5
- package/dist/harness-runtime/lib/tool-observations.mjs +20 -10
- package/dist/harness-runtime/lib/zcode-native-hook-ownership.mjs +109 -0
- package/dist/schemas/code-work-result.schema.json +2 -1
- package/dist/schemas/delivery-stage-result.schema.json +286 -0
- package/dist/schemas/distill-stage-result.schema.json +667 -0
- package/dist/schemas/distill-work-result.schema.json +428 -0
- package/dist/schemas/execution-bundle.schema.json +6 -2
- package/dist/schemas/interactive-stage-result.schema.json +47 -0
- package/dist/schemas/interactive-work-result.schema.json +63 -0
- package/dist/schemas/mb-fix-stage-result.schema.json +93 -0
- package/dist/schemas/project-flow-pack-manifest.schema.json +2 -2
- package/dist/schemas/review-fix-stage-result.schema.json +210 -0
- package/dist/schemas/run-control-receipt.schema.json +1 -0
- package/dist/services/assigned-lifecycle.js +382 -0
- package/dist/services/check-runner-evidence.js +45 -8
- package/dist/services/cli-operation-classifier.js +6 -1
- package/dist/services/code-checks.js +46 -29
- package/dist/services/codex-hook-delivery.js +11 -2
- package/dist/services/controller-execution.js +4 -1
- package/dist/services/controller-fanout.js +9 -4
- package/dist/services/controller-memory-worker.js +6 -4
- package/dist/services/controller-result-authority.js +122 -0
- package/dist/services/dashboard.js +4 -2
- package/dist/services/delivery-access.js +123 -0
- package/dist/services/delivery-approval.js +68 -0
- package/dist/services/delivery-artifact.js +86 -0
- package/dist/services/delivery-checks.js +178 -0
- package/dist/services/delivery-execution.js +181 -0
- package/dist/services/delivery-intents.js +212 -0
- package/dist/services/delivery-operation.js +81 -0
- package/dist/services/delivery-policy.js +91 -0
- package/dist/services/delivery-procedures.js +43 -0
- package/dist/services/delivery-request.js +99 -0
- package/dist/services/delivery-result.js +144 -0
- package/dist/services/delivery-stage-commands.js +26 -0
- package/dist/services/delivery-stage-entry.js +56 -0
- package/dist/services/delivery-stage-handlers.js +135 -0
- package/dist/services/delivery-stage-results.js +94 -0
- package/dist/services/descriptor-coordinator-session.js +3 -1
- package/dist/services/descriptor-run-start.js +24 -4
- package/dist/services/descriptor-stage-coordinator.js +78 -10
- package/dist/services/descriptor-stage-settlement.js +59 -14
- package/dist/services/diagnostic-repair-intake.js +123 -0
- package/dist/services/diagnostic-repair-source-intake.js +102 -0
- package/dist/services/diagnostic-report.js +30 -0
- package/dist/services/diagnostic-run-bundle.js +48 -9
- package/dist/services/diagnostic-snapshot.js +2 -2
- package/dist/services/diagnostic-stage-handlers.js +3 -2
- package/dist/services/diagnostic-stage-results.js +26 -0
- package/dist/services/diagnostic-start.js +56 -6
- package/dist/services/diagnostic-work-collection.js +3 -3
- package/dist/services/distill-document-publication.js +280 -0
- package/dist/services/distill-promotion.js +197 -0
- package/dist/services/distill-publication-custody.js +162 -0
- package/dist/services/distill-stage-handlers.js +249 -0
- package/dist/services/distill-stage-results.js +105 -0
- package/dist/services/distill-user-inputs.js +4 -0
- package/dist/services/distill-work-batch.js +42 -0
- package/dist/services/distill-work-collection.js +74 -0
- package/dist/services/distill-work-registration.js +93 -0
- package/dist/services/distill-work-results.js +98 -0
- package/dist/services/eval-snapshots.js +17 -7
- package/dist/services/external-work-launch.js +8 -2
- package/dist/services/flow-catalog.js +24 -0
- package/dist/services/frozen-engine.js +19 -2
- package/dist/services/generic-execution.js +4 -3
- package/dist/services/harness-adapter.js +2 -14
- package/dist/services/hook-memory-routing.js +1 -1
- package/dist/services/hooks.js +31 -6
- package/dist/services/hybrid-contracts.js +6 -1
- package/dist/services/installed-flow-source.js +37 -2
- package/dist/services/interactive-attached-session.js +215 -0
- package/dist/services/interactive-baseline-custody.js +54 -0
- package/dist/services/interactive-before-versions.js +148 -0
- package/dist/services/interactive-bundle-data.js +85 -0
- package/dist/services/interactive-checks.js +179 -0
- package/dist/services/interactive-execution.js +33 -0
- package/dist/services/interactive-finish-integration.js +236 -0
- package/dist/services/interactive-git-baseline.js +230 -0
- package/dist/services/interactive-git-dispatch.js +278 -0
- package/dist/services/interactive-input-delivery.js +82 -0
- package/dist/services/interactive-mutation-scope.js +231 -0
- package/dist/services/interactive-request.js +287 -0
- package/dist/services/interactive-run-bundle.js +109 -0
- package/dist/services/interactive-stage-entry.js +41 -0
- package/dist/services/interactive-stage-handlers.js +289 -0
- package/dist/services/interactive-stage-observation.js +136 -0
- package/dist/services/interactive-stage-results.js +111 -0
- package/dist/services/interactive-user-inputs.js +142 -0
- package/dist/services/interactive-work-batch.js +43 -0
- package/dist/services/interactive-work-collection.js +119 -0
- package/dist/services/interactive-work-completion.js +180 -0
- package/dist/services/interactive-work-context.js +249 -0
- package/dist/services/interactive-work-history.js +112 -0
- package/dist/services/interactive-work-registration.js +124 -0
- package/dist/services/interactive-work-results.js +115 -0
- package/dist/services/interactive-work-start.js +155 -0
- package/dist/services/lifecycle-invocations.js +214 -24
- package/dist/services/managed-daemon-binding.js +52 -1
- package/dist/services/memory-compose-handler.js +2 -22
- package/dist/services/memory-finish-integration.js +58 -17
- package/dist/services/memory-fix-finish-integration.js +129 -0
- package/dist/services/memory-fix-git-dispatch.js +139 -0
- package/dist/services/memory-fix-intake-custody.js +11 -0
- package/dist/services/memory-fix-intake-settlement.js +127 -0
- package/dist/services/memory-fix-publication.js +196 -0
- package/dist/services/memory-fix-repair-admission.js +116 -0
- package/dist/services/memory-fix-request.js +141 -0
- package/dist/services/memory-fix-run-bundle.js +83 -0
- package/dist/services/memory-fix-scope.js +78 -0
- package/dist/services/memory-fix-sources.js +85 -0
- package/dist/services/memory-fix-stage-entry.js +24 -0
- package/dist/services/memory-fix-stage-handlers.js +282 -0
- package/dist/services/memory-fix-stage-results.js +106 -0
- package/dist/services/memory-fix-workspace.js +127 -0
- package/dist/services/memory-flow-user-inputs.js +88 -0
- package/dist/services/memory-init-contexts.js +55 -5
- package/dist/services/memory-init-controller-start.js +20 -4
- package/dist/services/memory-layer.js +7 -3
- package/dist/services/memory-native-review-copy.js +27 -3
- package/dist/services/memory-repair-document-delta.js +1 -0
- package/dist/services/project-flow-source.js +195 -0
- package/dist/services/project-git-integration.js +92 -0
- package/dist/services/repair-intents.js +2 -7
- package/dist/services/retained-native-hook.js +245 -0
- package/dist/services/review-fix-handoff.js +82 -0
- package/dist/services/review-fix-intake-custody.js +17 -0
- package/dist/services/review-fix-stage-entry.js +43 -0
- package/dist/services/review-fix-stage-handlers.js +176 -0
- package/dist/services/review-fix-stage-results.js +86 -0
- package/dist/services/run-control-worker.js +42 -1
- package/dist/services/run-control.js +40 -5
- package/dist/services/run-controller-adapter.js +37 -3
- package/dist/services/run-controller-process.js +12 -4
- package/dist/services/run-controller-recovery.js +46 -6
- package/dist/services/run-controller-state.js +3 -2
- package/dist/services/run-controller.js +209 -43
- package/dist/services/run-recovery.js +31 -2
- package/dist/services/run-timeline.js +59 -0
- package/dist/services/runs.js +18 -31
- package/dist/services/sessions.js +2 -1
- package/dist/services/stage-handlers.js +55 -3
- package/dist/services/stage-lifecycle.js +4 -3
- package/dist/services/stage-pause.js +26 -9
- package/dist/services/stage-report-renderer.js +2 -6
- package/dist/services/technical-notes.js +31 -0
- package/dist/services/verification-matrix.js +1 -1
- package/dist/services/version-status.js +1 -1
- package/dist/services/vnext-code-review.js +74 -5
- package/dist/services/vnext-code.js +8 -32
- package/dist/services/vnext-fanout.js +53 -8
- package/dist/services/vnext-merge.js +10 -5
- package/dist/services/vnext-plan-review.js +1 -1
- package/dist/services/vnext-plan.js +1 -1
- package/dist/services/vnext-protocolize.js +1 -1
- package/dist/services/vnext-specify.js +10 -5
- package/dist/services/work-registry.js +252 -66
- package/dist/services/worktrees.js +7 -4
- package/dist/storage/database.js +2 -1
- package/dist/verification-components.json +82 -0
- package/package.json +3 -2
- 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
|
|
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
|
package/dist/cli/hook-ingress.js
CHANGED
|
@@ -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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
process.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
66
|
+
const result = handleCodexHook(context, { event, ...(root ? { projectRoot: root } : {}), stdin });
|
|
85
67
|
phase("committed");
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
} }
|
|
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
|
-
|
|
97
|
-
process.
|
|
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 =
|
|
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,
|