@deksden-com/dd-flow-cli 0.8.0-beta.135 → 0.9.0-beta.0

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 (40) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +31 -4
  3. package/dist/build-info.json +10 -10
  4. package/dist/cli/help.js +15 -63
  5. package/dist/cli/run-cli.js +55 -26
  6. package/dist/domain/stage-catalog.js +2 -2
  7. package/dist/domain/validation.js +1 -1
  8. package/dist/schemas/agent-profile.schema.json +17 -0
  9. package/dist/schemas/code-review-decision.schema.json +5 -3
  10. package/dist/schemas/code-work-batch.schema.json +3 -3
  11. package/dist/schemas/compatibility.schema.json +32 -0
  12. package/dist/schemas/flow-contract.schema.json +3 -2
  13. package/dist/schemas/merge-result.schema.json +15 -0
  14. package/dist/schemas/protocol-plan.schema.json +1 -1
  15. package/dist/schemas/stage-start-response.schema.json +4 -2
  16. package/dist/schemas/status-report.schema.json +76 -0
  17. package/dist/schemas/vnext-protocol-plan.schema.json +4 -4
  18. package/dist/services/cli-operation-classifier.js +1 -1
  19. package/dist/services/code-checks.js +125 -208
  20. package/dist/services/eval-snapshots.js +10 -1
  21. package/dist/services/harness-adapter.js +59 -0
  22. package/dist/services/hooks.js +87 -237
  23. package/dist/services/ids.js +18 -1
  24. package/dist/services/lifecycle-command.js +288 -0
  25. package/dist/services/merge-server.js +124 -0
  26. package/dist/services/prompts.js +16 -10
  27. package/dist/services/runs.js +7 -2
  28. package/dist/services/sessions.js +18 -27
  29. package/dist/services/stage-pause.js +13 -0
  30. package/dist/services/vnext-code-review.js +71 -20
  31. package/dist/services/vnext-code.js +131 -34
  32. package/dist/services/vnext-execution-profile.js +6 -3
  33. package/dist/services/vnext-merge.js +330 -0
  34. package/dist/services/vnext-plan-review.js +2 -2
  35. package/dist/services/vnext-plan.js +22 -38
  36. package/dist/services/vnext-specify.js +5 -2
  37. package/dist/services/vnext-workspace-policy.js +8 -2
  38. package/dist/services/work-registry.js +85 -34
  39. package/dist/storage/database.js +66 -0
  40. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # @deksden-com/dd-flow-cli
2
2
 
3
+ ## 0.9.0-beta.0
4
+
5
+ ### Minor Changes
6
+
7
+ - cef9b99: Add the vNext MERGE stage, project FIFO integration requests, same-session and
8
+ merge-server execution, integrated-tree verification receipts, and explicit
9
+ local delivery and cleanup policies. Upgrade the planned-check contract to
10
+ `code-check-profile@5` and project execution snapshots to
11
+ `project-execution@2`.
12
+
13
+ ### Patch Changes
14
+
15
+ - 7a97bdf: Use one shell-aware lifecycle parser across every harness hook so quoted
16
+ absolute launchers bind correctly while diagnostic text cannot trigger a
17
+ trusted lifecycle event.
18
+
19
+ ## 0.8.0
20
+
21
+ ### Patch Changes
22
+
23
+ - a092c98: Accept controlled OpenCode lifecycle events and cumulative root/child Session usage through the shared harness evidence path.
24
+ - fb2395a: Accept trusted lifecycle events from controlled zcode-acp sessions and retain harness-native root, child, parent, and execution-daemon identities in Session evidence.
25
+ - Promote the accepted SPECIFY-through-CODE-REVIEW runtime as stable 0.8.0 and align its shared compatibility, flow, stage-start and status schemas with Memory Bank 4.0.0.
26
+
27
+ ## 0.8.0-beta.138
28
+
29
+ ### Patch Changes
30
+
31
+ - Replace CODE and repair file allowlists with optional `planned_write_areas`:
32
+ soft coordination hints that serialize likely concurrent collisions but never
33
+ deny a necessary project-local edit. Record unexpected actual paths as
34
+ `coordination.drift` instead of rejecting the Work.
35
+ - Render the hard RUN workspace boundary separately from required reads,
36
+ discovery hints and planned coordination areas, so a worker can distinguish
37
+ semantic constraints from planning predictions.
38
+
39
+ ## 0.8.0-beta.137
40
+
41
+ ### Patch Changes
42
+
43
+ - Transfer a running coordinator Work to a fresh Session at a frozen
44
+ `new_session` stage boundary, while still rejecting a second Session that
45
+ tries to repeat the same stage.
46
+
47
+ ## 0.8.0-beta.136
48
+
49
+ ### Patch Changes
50
+
51
+ - Serialize the long CODE aggregate gate and reuse complete same-workspace
52
+ receipts after a client loses its command output, preventing a concurrent
53
+ retry from duplicating checks or colliding on receipt identifiers.
54
+
3
55
  ## 0.8.0-beta.135
4
56
 
5
57
  ### Patch Changes
package/README.md CHANGED
@@ -97,7 +97,7 @@ dd-flow prompt render \
97
97
 
98
98
  For a non-plan worker, replace `--plan-item` with `--task-file <path>` pointing to a `dd-flow/worker-task@1` manifest inside the selected RUN home. The manifest embeds one existing plan-item-shaped `task`, so validation and rendered artifacts remain identical to the compatibility route.
99
99
 
100
- The selected item must contain `execution_context` and `semantic_spine`. The renderer accepts only registered profiles, validates the active RUN/workspace and source paths, then persists `launch-prompt.md`, `prompt-stack.json` and `render-report.json` in the RUN stage. The stack and render report both retain the validated read/discovery/write references. It records project sources as paths rather than copying their contents. `required_read` and `discovery_boundary` may use a checked `run://<relative-path>` reference for a source inside the selected RUN home; write scopes remain project-local.
100
+ The selected item must contain `execution_context` and `semantic_spine`. The renderer accepts only registered profiles, validates the active RUN/workspace and source paths, then persists `launch-prompt.md`, `prompt-stack.json` and `render-report.json` in the RUN stage. The stack and render report both retain the validated read/discovery/coordination references. It records project sources as paths rather than copying their contents. `required_read` and `discovery_boundary` may use a checked `run://<relative-path>` reference for a source inside the selected RUN home. The RUN workspace is the hard write boundary; `planned_write_areas` are optional soft hints for concurrent-work coordination, never a file allowlist.
101
101
 
102
102
  Human help is available for the operator-facing command groups:
103
103
 
@@ -106,8 +106,7 @@ dd-flow --help
106
106
  dd-flow lane --help
107
107
  dd-flow lane workspace --help
108
108
  dd-flow lane lock --help
109
- dd-flow merge-queue --help
110
- dd-flow merge-queue wait-next --help
109
+ dd-flow merge --help
111
110
  dd-flow cleanup --help
112
111
  dd-flow codex home --help
113
112
  dd-flow worktree --help
@@ -189,7 +188,29 @@ When the task stops using the shared checkout:
189
188
  dd-flow lane lock release --project-root "$PROJECT_ROOT" --lane direct-main --worker-id "$PROTOCOL_ID" --path "$PWD" --lease-token "$TOKEN" --reason done --json
190
189
  ```
191
190
 
192
- ## Merge Queue Recipe
191
+ ## vNext MERGE recipe
192
+
193
+ CODE/CODE-REVIEW closure creates the `MRG-*` request and returns the exact
194
+ standalone stage-start command. Run it unchanged. The accepted packet then
195
+ provides the exact `merge apply` and `stage finish` commands; do not reconstruct
196
+ Git operations or lifecycle arguments manually.
197
+
198
+ For unattended execution, store a validated agent profile under
199
+ `DD_FLOW_HOME/agent-profiles/<id>.json` and run:
200
+
201
+ ```bash
202
+ dd-flow merge serve --agent-profile <id> --max-parallel-projects 4 --progress-jsonl
203
+ dd-flow merge server status --json
204
+ ```
205
+
206
+ The server preserves FIFO inside one project and may serve different projects
207
+ concurrently. Same-session and server routes use the same MRG, Work, checks and
208
+ stage report.
209
+
210
+ ## Classic merge queue recipe
211
+
212
+ The commands below belong to the classic protocol runtime. The vNext flow does
213
+ not call them or fall back to them.
193
214
 
194
215
  Prompts enqueue protocols only after explicit readiness signals exist:
195
216
 
@@ -368,6 +389,12 @@ The installed hook writes invocation markers, forwarded stderr, and the final
368
389
  exit code to `${DD_FLOW_HOME:-$HOME/.dd-flow}/logs/codex-hook.log`. An absent
369
390
  start marker means Codex did not invoke the hook; a start marker without a
370
391
  successful finish marker identifies a hook launch or handler failure.
392
+
393
+ All harness event handlers use the same lifecycle shell parser. It recognizes
394
+ `dd-flow` as the actual command word, including a quoted absolute executable,
395
+ and does not treat occurrences in search patterns, JSON, comments or heredoc
396
+ bodies as lifecycle calls. Real lifecycle calls joined to other shell commands
397
+ remain invalid and receive a standalone retry.
371
398
  `--project-root` belongs to installer/status commands, not the stored handler.
372
399
 
373
400
  ## Verification
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.8.0-beta.135",
4
- "cli_commit": "05b606d42418d944364abb013ce02f3e78149916",
5
- "built_at": "2026-08-30T15:27:46.996Z",
3
+ "cli_version": "0.9.0-beta.0",
4
+ "cli_commit": "ade3ebb2888bf804bec3879bbf7ed4600ff651fc",
5
+ "built_at": "2026-09-01T20:01:20.985Z",
6
6
  "built_with_canon": {
7
- "version": null,
8
- "commit": null,
9
- "flow_contract": null,
10
- "repo_root": null,
11
- "memorybank_root": null,
12
- "flow_root": null,
13
- "layout": null
7
+ "version": "4.0.0",
8
+ "commit": "bd6494ad11bca9544d9b483bd7fe37049e6d00fe",
9
+ "flow_contract": "dd-flow-canonical-2026-08",
10
+ "repo_root": "/Users/deksden/Documents/_Projects/dd-memorybank",
11
+ "memorybank_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank",
12
+ "flow_root": "/Users/deksden/Documents/_Projects/dd-memorybank/.memory-bank/dd-flow",
13
+ "layout": "dot_memory_bank"
14
14
  }
15
15
  }
package/dist/cli/help.js CHANGED
@@ -28,9 +28,8 @@ Core commands:
28
28
  plan status/item/reviews Inspect plans, update SQLite progress, or dispatch vNext grouped reviews.
29
29
  prompt render Render one bounded, RUN-local worker launch prompt.
30
30
  lane workspace/lock/status Manage shared workspace lanes and leases.
31
- merge status/one-shot/bundle Inspect, claim, or complete merge work from the current session.
32
- merge-worker status/start/stop Manage long-lived project merge workers.
33
- merge-queue status/claim/next Claim, wait for, complete, or fail merge jobs.
31
+ merge request/apply Inspect, route and execute vNext MERGE requests.
32
+ merge serve/status/stop Run or inspect the deterministic vNext merge server.
34
33
  session register/status/stop Register flow sessions and stop workers.
35
34
  dashboard data/render/open/refresh Render, refresh, open, and inspect project/global dashboards.
36
35
  schema validate Validate canonical dd-flow JSON data contracts.
@@ -60,8 +59,9 @@ Examples:
60
59
  dd-flow protocol transition PRT-001-demo --to implementation --payload-file transition.json --json
61
60
  dd-flow lane workspace set --project-root "$PWD" --lane merge --path "$PWD" --branch main --json
62
61
  dd-flow lane lock acquire --project-root "$PWD" --lane merge --worker-id worker-1 --path "$PWD" --ttl 300 --reason "merge worker" --json
63
- dd-flow merge status --project-root "$PWD" --json
64
- dd-flow merge-worker start --project-root "$PWD" --worker-id merge-worker --path "$PWD" --json
62
+ dd-flow merge request status MRG-001 --project-root "$PWD" --json
63
+ dd-flow merge apply MRG-001 --work WRK-001 --project-root "$PWD" --json --progress-jsonl
64
+ dd-flow merge serve --agent-profile codex-luna-xhigh --progress-jsonl
65
65
 
66
66
  Default output is human-readable. Use --json for hooks, prompts, scripts and other agent automation; JSON mode never mixes human progress text into stdout.
67
67
 
@@ -204,9 +204,9 @@ Usage:
204
204
  dd-flow work show <WORK-ID> --json
205
205
  dd-flow work start <WORK-ID> --project-root <root> --json
206
206
  dd-flow work finish <WORK-ID> (--result-file <path>|--result-stdin) --project-root <root> --json
207
- dd-flow work repair add --run <RUN-ID> --from-check <CHECK-ID> --origin-work <WORK-ID>... [--write-scope <path>...] --task-stdin --project-root <root> --json
207
+ dd-flow work repair add --run <RUN-ID> --from-check <CHECK-ID> --origin-work <WORK-ID>... --task-stdin --project-root <root> --json
208
208
 
209
- CODE workers receive their full accepted packet from work start. work finish validates the result and executes the packet's focused checks before completion. A failed aggregate CODE gate returns the exact failed receipt and repair command; repair add creates a new Work from the selected completed origin context plus that failure evidence. Exact existing project files named by the retained receipt output are added to its repair scope deterministically; repeat --write-scope only for an additional path not named there.`
209
+ CODE workers receive their full accepted packet from work start. work finish validates the result and executes the packet's focused checks before completion. A failed aggregate CODE gate returns the exact failed receipt and repair command; repair add creates a new Work from the selected completed origin context plus that failure evidence. Its planned write areas are coordination hints only; the repair may change any necessary project file inside its RUN workspace.`
210
210
  ],
211
211
  [
212
212
  "id",
@@ -363,53 +363,17 @@ The renderer accepts only registered profiles: code_implementation, documentatio
363
363
  ],
364
364
  [
365
365
  "merge",
366
- `dd-flow merge - current-session merge entrypoint helpers
366
+ `dd-flow merge - deterministic vNext integration queue and executor
367
367
 
368
368
  Usage:
369
- dd-flow merge status --project-root <root> [--path <workspace>] --json
370
- dd-flow merge one-shot --project-root <root> --worker-id <id> [--path <workspace>] --json
371
- dd-flow merge bundle status --project-root <root> --path <workspace> --json
372
- dd-flow merge bundle claim --project-root <root> --worker-id <id> --path <workspace> --json
373
- dd-flow merge bundle complete --project-root <root> --worker-id <id> --path <workspace> --summary <text> --json
374
- dd-flow merge bundle fail --project-root <root> --worker-id <id> --path <workspace> --reason <text> --requeue true|false --json
369
+ dd-flow merge request status <MRG-ID> --project-root <root> --json
370
+ dd-flow merge request route <MRG-ID> --mode same_session|server --reason <text> --project-root <root> --json
371
+ dd-flow merge apply <MRG-ID> --work <WRK-ID> --project-root <root> --json --progress-jsonl
372
+ dd-flow merge serve --agent-profile <id> [--once] [--poll-seconds <n>] [--max-parallel-projects <n>] --progress-jsonl
373
+ dd-flow merge server status --json
374
+ dd-flow merge server stop <MSV-ID> --json
375
375
 
376
- merge status reports active merge worker, claimed protocols, active merge lane lock, queue state and branch_context when --path is provided. merge one-shot keeps legacy single-protocol behavior and also returns branch_context. merge bundle commands operate on all ready protocols in the current branch/worktree: status is read-only, claim requires the merge lane lock owner, complete closes all claimed protocols after branch integration, and fail requeues or fails the claimed bundle.`
377
- ],
378
- [
379
- "merge-worker",
380
- `dd-flow merge-worker - manage long-lived project merge workers
381
-
382
- Usage:
383
- dd-flow merge-worker status --project-root <root> --json
384
- dd-flow merge-worker start --project-root <root> --worker-id <id> [--path <workspace>] [--branch <branch>] --json
385
- dd-flow merge-worker stop --project-root <root> [--worker-id <id>] --reason <text> --json
386
-
387
- start is idempotent: if a worker, claimed job, or active merge lock already exists it returns status instead of creating a duplicate worker. stop is no-op/status when no worker exists, refuses ambiguous multiple workers unless --worker-id is provided, stops idle workers immediately, and marks busy workers as stop-after-current while preserving the claimed job and lock.`
388
- ],
389
- [
390
- "merge-queue",
391
- `dd-flow merge-queue - inspect and process ready-for-merge protocols
392
-
393
- Usage:
394
- dd-flow merge-queue status --project-root <root> --json
395
- dd-flow merge-queue next --project-root <root> --worker-id <id> [--path <workspace>] --json
396
- dd-flow merge-queue claim <protocol-id> [--project-root <root>] --worker-id <id> [--path <workspace>] --json
397
- dd-flow merge-queue wait-next --project-root <root> --worker-id <id> [--path <workspace>] --timeout <seconds> --poll-interval <seconds> [--acquire-lock true] --json
398
- dd-flow merge-queue complete <protocol-id> [--project-root <root>] --worker-id <id> [--path <workspace>] --summary <text> --json
399
- dd-flow merge-queue note <protocol-id> [--project-root <root>] --worker-id <id> --summary <text> --json
400
- dd-flow merge-queue fail <protocol-id> [--project-root <root>] --worker-id <id> [--path <workspace>] --reason <text> --requeue true|false --json
401
- dd-flow merge-queue cancel <protocol-id> [--project-root <root>] --reason <text> [--worker-id <id>] [--path <workspace>] [--force] --json
402
-
403
- The worker must own the merge lane from the registered merge workspace before claiming, completing, or failing queued protocols. next remains FIFO; claim <protocol-id> atomically claims only that ready/requeued job. Outputs use queue_item/protocol/claim as the primary contract and retain job as a compatibility alias. note updates the final completion summary after post-complete cleanup and does not require a lane lock. --path defaults to the current working directory. Use --worker-id as the owner identity.
404
-
405
- For branch-level integration prefer:
406
- dd-flow merge bundle status --project-root "$PWD" --path "$PWD" --json
407
- dd-flow merge bundle claim --project-root "$PWD" --worker-id merge-worker --path "$PWD" --json
408
-
409
- Examples:
410
- dd-flow merge-queue wait-next --project-root "$PWD" --worker-id merge-worker --path "$PWD" --timeout 1200 --poll-interval 10 --acquire-lock true --json
411
- dd-flow merge-queue complete PRT-123 --project-root "$PWD" --worker-id merge-worker --path "$PWD" --summary "merged locally; cleanup follows" --json
412
- dd-flow merge-queue note PRT-123 --project-root "$PWD" --worker-id merge-worker --summary "merged, pushed, checked, and cleanup completed" --json`
376
+ CODE or CODE-REVIEW creates one MRG request and child MERGE Work. stage start waits in the shared project FIFO, reports progress, locks the execution target baseline and returns the exact apply/finish commands. merge apply is the only supported initial Git mutation. merge serve is a deterministic dispatcher: it launches a fresh Session through the configured harness adapter but does not perform agent work itself. It preserves FIFO inside each project and may serve different projects concurrently up to the explicit bound.`
413
377
  ],
414
378
  [
415
379
  "cleanup",
@@ -420,18 +384,6 @@ Usage:
420
384
  dd-flow cleanup apply --project-root <root> --plan-file <file> --reason <text> [--force] --json
421
385
 
422
386
  cleanup scan is read-only and returns deterministic findings plus plan.actions. cleanup apply validates project identity, action kinds, reason, and destructive force requirements before mutating runtime state, then reports requested/applied/changed/skipped action results. Worktree record cleanup marks a checkout removed only when its path is already absent; existing checkouts are kept instead of being silently deleted. Use this for stale queue jobs, missing protocol runtime snapshots, stale sessions, expired locks, and terminal-protocol worktree records.`
423
- ],
424
- [
425
- "merge-queue wait-next",
426
- `dd-flow merge-queue wait-next - wait for the next ready merge protocol
427
-
428
- Usage:
429
- dd-flow merge-queue wait-next --project-root <root> --worker-id <id> [--path <workspace>] --timeout <seconds> --poll-interval <seconds> [--acquire-lock true] --json
430
-
431
- Use this from a shell or supervisor to wait outside model tokens. Live merge workers should prefer long waits, for example --timeout 1200 --poll-interval 10, so the agent sleeps inside the CLI process instead of spending model tokens on frequent polling. If --acquire-lock true is used, wait-next first enters the generic FIFO lane waiter path (lane lock wait-acquire --lane merge), then heartbeats the merge lock while waiting for a ready protocol. A lock acquired by this command is released on timeout. If a protocol is ready, it is claimed atomically. If no protocol appears before the timeout, the command returns outcome: wait_timeout with queue_item: null and legacy job: null.
432
-
433
- Examples:
434
- dd-flow merge-queue wait-next --project-root "$PWD" --worker-id supervisor --path "$PWD" --timeout 1200 --poll-interval 10 --acquire-lock true --json`
435
387
  ],
436
388
  [
437
389
  "session",
@@ -17,6 +17,7 @@ import { getProjectConfigStatus, setProjectConfigValue } from "../services/confi
17
17
  import { getCanonStatus, registerCanonRoot, resolveCanonRoot } from "../services/canon.js";
18
18
  import { getRuntimeStatus } from "../services/status.js";
19
19
  import { getCliVersionReport } from "../services/build-info.js";
20
+ import { parseCommandArgs } from "../services/lifecycle-command.js";
20
21
  import { autoRefreshDashboards, dashboardData, getCmuxStatus, openDashboard, openGlobalDashboard, openProtocolDashboard, refreshAllDashboards, refreshDashboard, refreshGlobalDashboard, renderDashboard, renderGlobalDashboard } from "../services/dashboard.js";
21
22
  import { dashboardTargetSummary, preferredDashboardCommand, relatedDashboardCommands, resolveDashboardTarget } from "../services/dashboard-targets.js";
22
23
  import { getFlowSessionStatus, registerFlowSession, stopFlowSession, stopMergeWorker, syncFlowSessionUsage } from "../services/sessions.js";
@@ -38,6 +39,8 @@ import { finishVnextPlan, isVnextPlanRun, startVnextPlan } from "../services/vne
38
39
  import { dispatchVnextPlanReview, finishVnextPlanReview, isVnextPlanReviewRun, recordVnextPlanReviewCapacity, startVnextPlanReview } from "../services/vnext-plan-review.js";
39
40
  import { addVnextCodeRepair, finishVnextCode, startVnextCode } from "../services/vnext-code.js";
40
41
  import { addVnextCodeReviewRepair, finishVnextCodeReview, isVnextCodeReviewRun, startVnextCodeReview } from "../services/vnext-code-review.js";
42
+ import { applyVnextMerge, finishVnextMerge, getVnextMergeRequest, isVnextMergeRun, routeVnextMergeRequest, startVnextMerge } from "../services/vnext-merge.js";
43
+ import { mergeServerStatus, serveMergeRequests, stopMergeServer } from "../services/merge-server.js";
41
44
  import { getVnextFanoutStatus } from "../services/vnext-fanout.js";
42
45
  import { pauseStageForUser, resumeStageAfterUser } from "../services/stage-pause.js";
43
46
  import { blockStageForRuntime, unblockStageAfterRuntimeRepair } from "../services/stage-blocker.js";
@@ -217,7 +220,7 @@ function progressForCommand(args) {
217
220
  ];
218
221
  }
219
222
  if ((family === "cleanup" && command === "apply") ||
220
- (family === "merge" && command === "one-shot") ||
223
+ family === "merge" ||
221
224
  family === "merge-worker" ||
222
225
  (family === "protocol" && command === "cancel") ||
223
226
  (family === "project" && command === "archive") ||
@@ -666,7 +669,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
666
669
  projectRoot: requiredScopeProjectRoot(scopeProjectRoot, "work repair add"),
667
670
  runId: requiredOption(parsed, "run"),
668
671
  findingIds: findings,
669
- writeScope: parsed.options.get("write-scope") ?? [],
672
+ checkRefsByFinding: Object.fromEntries(findings.map((finding) => [finding, parsed.options.get("check-ref") ?? []])),
670
673
  objective: await readStdin(io.stdin)
671
674
  });
672
675
  if (!hasOption(parsed, "from-check"))
@@ -678,7 +681,6 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
678
681
  runId: requiredOption(parsed, "run"),
679
682
  checkReceiptId: requiredOption(parsed, "from-check"),
680
683
  originWorkIds: origins,
681
- writeScope: parsed.options.get("write-scope") ?? [],
682
684
  objective: await readStdin(io.stdin)
683
685
  });
684
686
  }
@@ -783,6 +785,10 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
783
785
  hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
784
786
  return attachExternalStageContext(startVnextCodeReview(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
785
787
  }
788
+ if (!bootstrap && stage === "merge" && isVnextMergeRun(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id") })) {
789
+ hookEventId ??= claimVnextStageStart(requiredPosition(parsed, 0, "run-id"));
790
+ return attachExternalStageContext(await startVnextMerge(context, { projectRoot, runId: requiredPosition(parsed, 0, "run-id"), hookEventId, progress: commandProgress(io, output), ...(contextSha256 ? { contextSha256 } : {}), ...(externalContext ? { externalContext } : {}) }), externalContext);
791
+ }
786
792
  if (intakeStdin) {
787
793
  throw new AppError("usage", "--intake-stdin is only available for vNext SPECIFY", 2);
788
794
  }
@@ -855,6 +861,11 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
855
861
  throw new AppError("usage", "vNext CODE-REVIEW uses --decision-file", 2);
856
862
  return await finishVnextCodeReview(context, { projectRoot, runId, ...(optionalOption(parsed, "decision-file") ? { decisionFile: optionalOption(parsed, "decision-file") } : {}), progress: commandProgress(io, output) });
857
863
  }
864
+ if (stage === "merge" && isVnextMergeRun(context, { projectRoot, runId })) {
865
+ if (resultStdin || semanticFile || resultFile || hasOption(parsed, "outcome"))
866
+ throw new AppError("usage", "vNext MERGE uses the Work result path returned by stage start", 2);
867
+ return await finishVnextMerge(context, { projectRoot, runId, requestId: requiredOption(parsed, "request"), workId: requiredOption(parsed, "work"), progress: commandProgress(io, output) });
868
+ }
858
869
  if (hasOption(parsed, "outcome") || hasOption(parsed, "result-file") || resultStdin) {
859
870
  throw new AppError("usage", "--outcome, --result-file and --result-stdin are only available for vNext SPECIFY", 2);
860
871
  }
@@ -922,7 +933,7 @@ async function dispatch(args, context, io, scopeProjectRoot = null, classificati
922
933
  return dispatchMergeQueue(context, command, parsed, requiredScopeProjectRootForOptional(scopeProjectRoot, command, parsed));
923
934
  }
924
935
  if (family === "merge") {
925
- return dispatchMerge(context, command, parsed);
936
+ return await dispatchMerge(context, command, parsed, commandProgress(io, output));
926
937
  }
927
938
  if (family === "merge-worker") {
928
939
  return dispatchMergeWorker(context, command, parsed);
@@ -1158,7 +1169,35 @@ function dispatchMergeQueue(context, command, parsed, scopeProjectRoot) {
1158
1169
  }
1159
1170
  throw new AppError("usage", `Unknown merge-queue command: ${command ?? "<empty>"}`, 2);
1160
1171
  }
1161
- function dispatchMerge(context, command, parsed) {
1172
+ async function dispatchMerge(context, command, parsed, progress) {
1173
+ if (command === "serve") {
1174
+ const pollSeconds = optionalPositiveNumber(parsed, "poll-seconds");
1175
+ const maxParallelProjects = optionalPositiveNumber(parsed, "max-parallel-projects", true);
1176
+ return await serveMergeRequests(context, { profileId: requiredOption(parsed, "agent-profile"), once: hasOption(parsed, "once"), ...(pollSeconds ? { pollSeconds } : {}), ...(maxParallelProjects ? { maxParallelProjects } : {}), ...(progress ? { progress } : {}) });
1177
+ }
1178
+ if (command === "server") {
1179
+ const action = requiredPosition(parsed, 0, "server action");
1180
+ if (action === "status")
1181
+ return mergeServerStatus(context);
1182
+ if (action === "stop")
1183
+ return stopMergeServer(context, { serverId: requiredPosition(parsed, 1, "server-id") });
1184
+ throw new AppError("usage", `Unknown merge server action: ${action}`, 2);
1185
+ }
1186
+ if (command === "apply")
1187
+ return applyVnextMerge(context, { projectRoot: requiredOption(parsed, "project-root"), requestId: requiredPosition(parsed, 0, "merge-request-id"), workId: requiredOption(parsed, "work"), ...(progress ? { progress } : {}) });
1188
+ if (command === "request") {
1189
+ const action = requiredPosition(parsed, 0, "request action");
1190
+ const requestId = requiredPosition(parsed, 1, "merge-request-id");
1191
+ if (action === "status")
1192
+ return getVnextMergeRequest(context, { projectRoot: requiredOption(parsed, "project-root"), requestId });
1193
+ if (action === "route") {
1194
+ const mode = requiredOption(parsed, "mode");
1195
+ if (mode !== "same_session" && mode !== "server")
1196
+ throw new AppError("usage", "--mode must be same_session or server", 2);
1197
+ return routeVnextMergeRequest(context, { projectRoot: requiredOption(parsed, "project-root"), requestId, mode, reason: requiredOption(parsed, "reason") });
1198
+ }
1199
+ throw new AppError("usage", `Unknown merge request action: ${action}`, 2);
1200
+ }
1162
1201
  if (command === "status") {
1163
1202
  return getMergeWorkerStatus(context, { projectRoot: requiredOption(parsed, "project-root"), workspacePath: optionalOption(parsed, "path") });
1164
1203
  }
@@ -1989,26 +2028,7 @@ function parseFlowFlagOverrides(values) {
1989
2028
  return result;
1990
2029
  }
1991
2030
  function parseArgs(args) {
1992
- const positional = [];
1993
- const options = new Map();
1994
- for (let index = 0; index < args.length; index += 1) {
1995
- const value = args[index];
1996
- if (value?.startsWith("--")) {
1997
- const key = value.slice(2);
1998
- const next = args[index + 1];
1999
- if (!next || next.startsWith("--")) {
2000
- options.set(key, [...(options.get(key) ?? []), flagValue]);
2001
- }
2002
- else {
2003
- options.set(key, [...(options.get(key) ?? []), next]);
2004
- index += 1;
2005
- }
2006
- }
2007
- else if (value) {
2008
- positional.push(value);
2009
- }
2010
- }
2011
- return { positional, options };
2031
+ return parseCommandArgs(args);
2012
2032
  }
2013
2033
  function requiredPosition(parsed, index, label) {
2014
2034
  const value = parsed.positional[index];
@@ -2082,6 +2102,15 @@ function optionalNumber(parsed, key) {
2082
2102
  }
2083
2103
  return parsedNumber;
2084
2104
  }
2105
+ function optionalPositiveNumber(parsed, key, integer = false) {
2106
+ const value = optionalNumber(parsed, key);
2107
+ if (typeof value === "undefined")
2108
+ return undefined;
2109
+ if (value <= 0 || (integer && !Number.isInteger(value))) {
2110
+ throw new AppError("validation", `--${key} must be a positive${integer ? " integer" : ""} number`, 2);
2111
+ }
2112
+ return value;
2113
+ }
2085
2114
  function requiredWorkerId(parsed) {
2086
2115
  return requiredOption(parsed, "worker-id");
2087
2116
  }
@@ -2176,7 +2205,7 @@ function projectRootForMutation(context, args, result, resolvedScope) {
2176
2205
  if (family === "merge-queue") {
2177
2206
  return projectRootForMergeQueueMutation(context, command, parsed);
2178
2207
  }
2179
- if (family === "merge" && command === "one-shot") {
2208
+ if (family === "merge" && ["one-shot", "apply", "request"].includes(command ?? "")) {
2180
2209
  return requiredOption(parsed, "project-root");
2181
2210
  }
2182
2211
  if (family === "merge-worker" && ["start", "stop"].includes(command ?? "")) {
@@ -5,8 +5,8 @@ export const vnextStages = [
5
5
  { id: "plan", directory: "03-plan", result: "stage-report.json", schema: "dd-flow/stage-report@2", next: ["plan-review", "code"] },
6
6
  { id: "plan-review", directory: "04-plan-review", result: "decision.json", schema: "dd-flow/plan-review-decision@3", next: ["code"] },
7
7
  { id: "code", directory: "05-code", result: "code-verification.json", schema: "dd-flow/code-verification@2", next: ["code-review", "merge"] },
8
- { id: "code-review", directory: "06-code-review", result: "decision.json", schema: "dd-flow/code-review-decision@2", next: ["merge"] },
9
- { id: "merge", directory: "07-merge", result: "merge-result.json", schema: "dd-flow/merge-result@1", next: [] }
8
+ { id: "code-review", directory: "06-code-review", result: "decision.json", schema: "dd-flow/code-review-decision@3", next: ["merge"] },
9
+ { id: "merge", directory: "07-merge", result: "stage-report.json", schema: "dd-flow/stage-report@2", next: [] }
10
10
  ];
11
11
  export function vnextStage(id) {
12
12
  const stage = vnextStages.find((candidate) => candidate.id === id);
@@ -113,7 +113,7 @@ function validateExecutionContext(input, itemId) {
113
113
  prompt_profile: requireString(context.prompt_profile, `Plan item ${itemId} execution_context.prompt_profile`),
114
114
  required_read: requireStringArray(context.required_read, `Plan item ${itemId} execution_context.required_read`),
115
115
  discovery_boundary: requireStringArray(context.discovery_boundary, `Plan item ${itemId} execution_context.discovery_boundary`),
116
- write_scope: requireStringArray(context.write_scope, `Plan item ${itemId} execution_context.write_scope`),
116
+ planned_write_areas: requireStringArray(context.planned_write_areas, `Plan item ${itemId} execution_context.planned_write_areas`),
117
117
  checks: requireStringArray(context.checks, `Plan item ${itemId} execution_context.checks`)
118
118
  };
119
119
  }
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "dd-flow/agent-profile@1",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schema_id", "id", "harness", "provider", "model", "reasoning", "mode", "permission"],
7
+ "properties": {
8
+ "schema_id": {"const": "dd-flow/agent-profile@1"},
9
+ "id": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"},
10
+ "harness": {"enum": ["codex", "zcode", "agy", "opencode", "grok"]},
11
+ "provider": {"type": "string", "minLength": 1},
12
+ "model": {"type": "string", "minLength": 1},
13
+ "reasoning": {"type": "string", "minLength": 1},
14
+ "mode": {"type": "string", "minLength": 1},
15
+ "permission": {"enum": ["allow", "deny"]}
16
+ }
17
+ }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "dd-flow/code-review-decision@2",
3
+ "$id": "dd-flow/code-review-decision@3",
4
4
  "type": "object",
5
5
  "additionalProperties": false,
6
6
  "required": ["schema_id", "summary", "findings"],
7
7
  "properties": {
8
- "schema_id": {"const": "dd-flow/code-review-decision@2"},
8
+ "schema_id": {"const": "dd-flow/code-review-decision@3"},
9
9
  "summary": {"type": "string", "minLength": 1},
10
10
  "findings": {
11
11
  "type": "array",
@@ -17,9 +17,11 @@
17
17
  "finding_ref": {"type": "string", "pattern": "^WRK-[^/]+/FIND-[0-9]{3}$"},
18
18
  "disposition": {"enum": ["fix", "defer", "reject", "duplicate"]},
19
19
  "reason": {"type": "string", "minLength": 1},
20
+ "check_refs": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}},
20
21
  "def_id": {"type": "string", "minLength": 1},
21
22
  "duplicate_of": {"type": "string", "minLength": 1}
22
- }
23
+ },
24
+ "allOf": [{"if": {"properties": {"disposition": {"const": "fix"}}}, "then": {"required": ["check_refs"]}}]
23
25
  }
24
26
  }
25
27
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
- "$id": "dd-flow/code-work-batch@4",
3
+ "$id": "dd-flow/code-work-batch@5",
4
4
  "type": "object",
5
5
  "additionalProperties": false,
6
6
  "required": ["schema_id", "sources", "works"],
7
7
  "properties": {
8
- "schema_id": {"const": "dd-flow/code-work-batch@4"},
8
+ "schema_id": {"const": "dd-flow/code-work-batch@5"},
9
9
  "sources": {"type": "array", "minItems": 1, "items": {"type": "object", "additionalProperties": false, "required": ["plan_id", "protocol_id", "revision", "sha256"], "properties": {"plan_id": {"type": "string", "minLength": 1}, "protocol_id": {"type": "string", "minLength": 1}, "revision": {"type": "integer", "minimum": 1}, "sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}}}},
10
10
  "works": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/work"}}
11
11
  },
@@ -19,6 +19,6 @@
19
19
  "check": {"type": "object", "additionalProperties": false, "required": ["id", "command", "purpose", "run_at", "availability"], "properties": {"id": {"type": "string", "pattern": "^CHK-[A-Za-z0-9-]+$"}, "command": {"type": "string", "minLength": 1}, "purpose": {"type": "string", "minLength": 1}, "run_at": {"enum": ["work", "code", "readiness", "merge", "release", "external"]}, "availability": {"enum": ["available", "planned"]}, "provided_by": {"type": "string", "minLength": 1}, "definition": {"type": "string", "minLength": 1}, "required_artifacts": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}}, "allOf": [{"if": {"properties": {"availability": {"const": "planned"}}, "required": ["availability"]}, "then": {"required": ["provided_by", "definition"], "properties": {"command": {"pattern": "^@check/"}}}}]},
20
20
  "documentUpdate": {"type": "object", "additionalProperties": false, "required": ["path", "action", "owner", "reason", "baseline_sha256"], "properties": {"path": {"type": "string", "minLength": 1}, "action": {"enum": ["create", "update"]}, "owner": {"type": "string", "minLength": 1}, "reason": {"type": "string", "minLength": 1}, "baseline_sha256": {"type": ["string", "null"], "pattern": "^[a-f0-9]{64}$"}}},
21
21
  "repair": {"type": "object", "additionalProperties": false, "required": ["origin_work_ids"], "anyOf": [{"required": ["check_receipt_id", "failure_receipt_path"]}, {"required": ["review_finding_ids", "review_evidence_refs"]}], "properties": {"origin_work_ids": {"$ref": "#/$defs/strings"}, "check_receipt_id": {"type": "string", "minLength": 1}, "failure_receipt_path": {"type": "string", "minLength": 1}, "review_finding_ids": {"$ref": "#/$defs/strings"}, "review_evidence_refs": {"$ref": "#/$defs/strings"}}},
22
- "work": {"type": "object", "additionalProperties": false, "required": ["schema_id", "key", "launch_policy", "source", "task", "semantic_spine", "requirements", "acceptance", "document_updates", "required_read", "discovery_boundary", "write_scope", "checks", "provides_checks", "stop_conditions", "depends_on", "result_schema"], "properties": {"schema_id": {"const": "dd-flow/code-work-packet@4"}, "key": {"type": "string", "minLength": 1}, "launch_policy": {"const": "fresh_agent_required"}, "source": {"$ref": "#/$defs/source"}, "repair": {"$ref": "#/$defs/repair"}, "task": {"type": "string", "minLength": 1}, "semantic_spine": {"$ref": "#/$defs/spine"}, "requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/obligation"}}, "acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}}, "document_updates": {"type": "array", "items": {"$ref": "#/$defs/documentUpdate"}}, "required_read": {"$ref": "#/$defs/strings"}, "discovery_boundary": {"$ref": "#/$defs/strings"}, "write_scope": {"$ref": "#/$defs/strings"}, "checks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/check"}}, "provides_checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "stop_conditions": {"$ref": "#/$defs/strings"}, "depends_on": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "result_schema": {"const": "dd-flow/code-work-result@2"}}}
22
+ "work": {"type": "object", "additionalProperties": false, "required": ["schema_id", "key", "launch_policy", "source", "task", "semantic_spine", "requirements", "acceptance", "document_updates", "required_read", "discovery_boundary", "planned_write_areas", "checks", "provides_checks", "stop_conditions", "depends_on", "result_schema"], "properties": {"schema_id": {"const": "dd-flow/code-work-packet@5"}, "key": {"type": "string", "minLength": 1}, "launch_policy": {"const": "fresh_agent_required"}, "source": {"$ref": "#/$defs/source"}, "repair": {"$ref": "#/$defs/repair"}, "task": {"type": "string", "minLength": 1}, "semantic_spine": {"$ref": "#/$defs/spine"}, "requirements": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/obligation"}}, "acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}}, "document_updates": {"type": "array", "items": {"$ref": "#/$defs/documentUpdate"}}, "required_read": {"$ref": "#/$defs/strings", "description": "Mandatory starting sources, not a read allowlist."}, "discovery_boundary": {"$ref": "#/$defs/strings", "description": "Likely discovery areas; additional project-local reads are allowed."}, "planned_write_areas": {"type": "array", "items": {"type": "string", "minLength": 1}, "description": "Soft coordination hints only. A worker may change any necessary project file inside the RUN workspace."}, "checks": {"type": "array", "minItems": 1, "items": {"$ref": "#/$defs/check"}}, "provides_checks": {"type": "array", "items": {"$ref": "#/$defs/check"}}, "stop_conditions": {"$ref": "#/$defs/strings", "description": "Hard semantic or external stop conditions; path prediction drift is not a stop condition."}, "depends_on": {"type": "array", "uniqueItems": true, "items": {"type": "string", "minLength": 1}}, "result_schema": {"const": "dd-flow/code-work-result@2"}}}
23
23
  }
24
24
  }
@@ -51,6 +51,38 @@
51
51
  "status": { "type": "string", "minLength": 1 }
52
52
  }
53
53
  },
54
+ "upgrade_contract": {
55
+ "type": "object",
56
+ "additionalProperties": false,
57
+ "required": ["model", "authorization", "storage_mutation", "run_creation", "post_sync_routing", "contexts", "order"],
58
+ "properties": {
59
+ "model": { "const": "dual_context_target_routing" },
60
+ "authorization": { "const": "command_allowlist" },
61
+ "storage_mutation": { "const": "explicit_migration_apply_only" },
62
+ "run_creation": { "const": "after_storage_readiness" },
63
+ "post_sync_routing": { "const": "marker_free_project_current" },
64
+ "contexts": {
65
+ "type": "array",
66
+ "const": ["project_current", "upgrade_target", "effective_execution"]
67
+ },
68
+ "order": {
69
+ "type": "array",
70
+ "const": [
71
+ "pin_target_canon",
72
+ "verify_target_router",
73
+ "resolve_contexts",
74
+ "read_only_diagnostics",
75
+ "check_shared_home_activity",
76
+ "verify_backup",
77
+ "plan_adjacent_migration",
78
+ "apply_migration_explicitly",
79
+ "create_upgrade_run",
80
+ "sync_canonical_layer",
81
+ "verify_marker_free_normal_route"
82
+ ]
83
+ }
84
+ }
85
+ },
54
86
  "contracts": {
55
87
  "type": "object",
56
88
  "additionalProperties": false,
@@ -34,12 +34,13 @@
34
34
  "specifications": {
35
35
  "type": "array",
36
36
  "minItems": 3,
37
+ "uniqueItems": true,
37
38
  "items": {
38
39
  "type": "object",
39
40
  "additionalProperties": false,
40
41
  "required": ["id", "version", "sha256"],
41
42
  "properties": {
42
- "id": {"enum": ["SPC-004", "SPC-005", "SPC-006"]},
43
+ "id": {"enum": ["SPC-004", "SPC-005", "SPC-006", "SPC-009", "SPC-010", "SPC-011"]},
43
44
  "version": {"type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$"},
44
45
  "sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"}
45
46
  }
@@ -180,7 +181,7 @@
180
181
  "source_of_truth": {"const": "canonical prompts and schemas"},
181
182
  "runtime_owner": {"const": "dd-flow-cli"},
182
183
  "lint_owner": {"const": "mb-lint"},
183
- "integrated_gate": {"const": "compatible releases plus clean specify-to-plan eval"}
184
+ "integrated_gate": {"type": "string", "minLength": 1}
184
185
  }
185
186
  }
186
187
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "dd-flow/merge-result@1",
4
+ "type": "object",
5
+ "additionalProperties": false,
6
+ "required": ["schema_id", "outcome", "summary", "conflict_resolution", "verification_summary", "residual_risks"],
7
+ "properties": {
8
+ "schema_id": {"const": "dd-flow/merge-result@1"},
9
+ "outcome": {"enum": ["completed", "blocked"]},
10
+ "summary": {"type": "string", "minLength": 1},
11
+ "conflict_resolution": {"type": "string"},
12
+ "verification_summary": {"type": "string", "minLength": 1},
13
+ "residual_risks": {"type": "array", "items": {"type": "string", "minLength": 1}}
14
+ }
15
+ }