@deksden-com/dd-flow-cli
v0.2.0
Published
Mechanical runtime CLI for dd-flow workflows.
Readme
dd-flow-cli
dd-flow-cli is the mechanical control layer for dd-flow workflows.
It does not replace Memory Bank prompts and does not make product, design, merge, or verification judgments. Prompts own intent, route selection, planning depth, evidence meaning, and semantic readiness. The CLI owns explicit local state: projects, protocols, Codex session bindings, transitions, worktree records, lanes, locks, merge queue jobs, hook records, and audit events.
Runtime Model
Root local database:
~/.dd-flow/db.sqliteNew projects use typed full ids and aliases:
PRJ-001-dd-flow-playground
PRJ-001Project-scoped runtime protocol state:
~/.dd-flow/projects/<PRJ-ID-slug>/runtime/protocols/<protocol-id>/state.json
~/.dd-flow/projects/<PRJ-ID-slug>/runtime/protocols/<protocol-id>/plan.jsonProject Memory Bank files under .memory-bank/protocol/PRT-.../ are durable documentation and evidence snapshots. Runtime commands such as status, transition, merge completion, cancel, and cleanup do not require a feature-worktree-local state.json to exist.
Project flow contract:
.memory-bank/dd-flow/flow-contract.json
.memory-bank/dd-flow/flow-contract.yamlThe flow contract defines mechanical protocol stages, allowed transitions, the ready-for-merge gate, merge completion target stage, and the default first next_action. If no file exists, the built-in canonical contract is used. JSON takes precedence over YAML when both are present. The default canonical stages are prime, readiness, and integration; legacy g0, m1, and m2 inputs are normalized as aliases for local migration safety.
project_root is the stable repository identity in dd-flow state. workspace_path is the concrete checkout where an agent is about to work. They can be the same path, or different paths when a feature worktree, merge checkout, direct integration checkout, or temporary fixture workspace is used.
Basic Commands
Inspect runtime and canonical Memory Bank discovery:
dd-flow status --project-root "$PWD" --json
dd-flow canon resolve --json
dd-flow canon register --root "$DD_MEMORYBANK" --json
dd-flow canon status --jsoncanon resolve checks explicit --root, then DD_MEMORYBANK, then the globally registered value in DD_FLOW_HOME, then known nearby dd-memorybank checkouts from the current working directory. If two different valid canonical roots are discovered, it fails closed with structured blockers and bootstrap hints. Use DD_MEMORYBANK for the local canonical dd-memorybank checkout.
Register a project:
dd-flow project register --root "$PWD" --json
dd-flow project status --root "$PWD" --json
dd-flow project resolve PRJ-001 --json
dd-flow project archive PRJ-001 --reason "old missing worktree root" --json
dd-flow project archive --root /abs/old/deleted/root --reason "old missing worktree root" --json
dd-flow project migrate-ids --root "$PWD" --json
dd-flow project migrate-ids --root "$PWD" --apply --jsonUse project archive for old registered roots that no longer exist, especially historical feature-worktree roots from earlier runtime layouts. Archiving does not delete protocols, queue rows, or audit history; it removes the project from active global dashboards and keeps it in the archived section for traceability. Re-registering the same existing root reactivates it.
migrate-ids is explicit on purpose. It reports the old hash-based local id and the planned typed id in dry-run mode, then updates project references only with --apply.
Register and inspect a protocol:
dd-flow protocol register HANDSHAKE-001 --project-root "$PWD" --json
dd-flow protocol status PRT-HANDSHAKE-001 --jsonAttach and update a plan:
dd-flow plan set PRT-HANDSHAKE-001 --file plan.json --json
dd-flow plan item start PRT-HANDSHAKE-001 P1 --json
dd-flow plan item done PRT-HANDSHAKE-001 P1 --summary "Implemented" --evidence "pnpm test" --jsonHuman help is available for the operator-facing command groups:
dd-flow --help
dd-flow lane --help
dd-flow lane workspace --help
dd-flow lane lock --help
dd-flow merge-queue --help
dd-flow merge-queue wait-next --help
dd-flow cleanup --help
dd-flow codex home --help
dd-flow worktree --helpLanes And Locks
A lane is a named shared operational workspace inside a project. A lane lock is a time-bound ownership lease on that workspace. The CLI enforces one active non-expired lock per project/lane; prompts decide whether a task should use a feature worktree, direct-main, or merge.
Register and check a lane workspace:
dd-flow lane workspace set --project-root "$PROJECT_ROOT" --lane merge --path "$WORKSPACE" --branch main --json
dd-flow lane workspace check --project-root "$PROJECT_ROOT" --lane merge --path "$PWD" --jsonAcquire, heartbeat, and release a lane lock:
dd-flow lane lock acquire --project-root "$PROJECT_ROOT" --lane merge --worker-id "$WORKER_ID" --path "$WORKSPACE" --ttl 300 --reason "merge worker" --json
dd-flow lane lock heartbeat --project-root "$PROJECT_ROOT" --lane merge --worker-id "$WORKER_ID" --path "$WORKSPACE" --lease-token "$TOKEN" --json
dd-flow lane lock release --project-root "$PROJECT_ROOT" --lane merge --worker-id "$WORKER_ID" --path "$WORKSPACE" --lease-token "$TOKEN" --reason done --jsonInspect lane state:
dd-flow lane status --project-root "$PROJECT_ROOT" --json
dd-flow lane lock status --project-root "$PROJECT_ROOT" --lane merge --jsonBranch checks are factual diagnostics. Lock mutation and merge queue claiming validate the registered workspace path mechanically. A mismatch can help a prompt or operator notice the wrong checkout, but the CLI does not decide whether the route is semantically allowed.
Direct Main Recipe
Use direct-main only after the prompt or operator has chosen direct integration work for a small, low-risk task.
dd-flow lane workspace set --project-root "$PROJECT_ROOT" --lane direct-main --path "$PROJECT_ROOT" --branch main --json
dd-flow lane workspace check --project-root "$PROJECT_ROOT" --lane direct-main --path "$PWD" --json
dd-flow lane lock acquire --project-root "$PROJECT_ROOT" --lane direct-main --worker-id "$PROTOCOL_ID" --path "$PWD" --ttl 300 --reason "direct integration work" --jsonWhen the task stops using the shared checkout:
dd-flow lane lock release --project-root "$PROJECT_ROOT" --lane direct-main --worker-id "$PROTOCOL_ID" --path "$PWD" --lease-token "$TOKEN" --reason done --jsonMerge Queue Recipe
Prompts enqueue protocols only after explicit readiness signals exist:
dd-flow protocol ready-for-merge PRT-... --json
dd-flow merge-queue status --project-root "$PROJECT_ROOT" --jsonA merge worker must own the merge lane before claiming jobs:
dd-flow lane workspace set --project-root "$PROJECT_ROOT" --lane merge --path "$MERGE_WORKSPACE" --branch main --json
dd-flow lane lock acquire --project-root "$PROJECT_ROOT" --lane merge --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --ttl 300 --reason "merge worker" --json
dd-flow merge-queue next --project-root "$PROJECT_ROOT" --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --jsonFor supervisor-style waiting outside model tokens:
dd-flow merge-queue wait-next --project-root "$PROJECT_ROOT" --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --timeout 1200 --poll-interval 10 --acquire-lock true --jsonWith --acquire-lock true, wait-next heartbeats the merge lane lock while it waits and releases a lock it acquired itself when the wait times out. Live merge workers should prefer long waits, for example 1200 seconds / 20 minutes, so the agent sleeps inside the CLI process instead of spending model tokens on frequent polling.
Complete or fail the claimed job:
dd-flow merge-queue complete PRT-... --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --summary "merged locally" --json
dd-flow merge-queue fail PRT-... --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --reason "conflict requires owner" --requeue true --jsonSuccessful merge-queue complete is terminal for the protocol runtime: it writes stage: closed, status: closed, and next_action: none. Post-complete cleanup can still update the queue reason with merge-queue note, but dashboards must no longer show the protocol as active work.
Cancel stale or intentionally abandoned queue work explicitly:
dd-flow merge-queue cancel PRT-... --reason "abandoned rerun" --json
dd-flow merge-queue cancel PRT-... --worker-id "$WORKER_ID" --path "$MERGE_WORKSPACE" --reason "owner cancelled claimed job" --jsonFlow sessions are registered explicitly with dd-flow session register; merge queue commands use --worker-id only. Codex prompts should prefer a JSON payload file:
dd-flow session register --payload-file ".tasks/dd-flow/session-payload.json" --json--payload-base64 remains supported for callers that can pass a literal value, but it must not use shell variables in Codex-hooked sessions because PreToolUse receives the raw command before shell expansion.
For feature worktrees, keep the stable project identity separate from the checkout where the agent works:
dd-flow protocol register PRT-... --project-root "$PROJECT_ROOT" --workspace-path "$FEATURE_WORKTREE" --jsonCodex hooks enforce merge-role boundaries mechanically. A normal planning or implementation session may mark a protocol ready for merge, but it may not claim merge queue jobs, mutate the merge lane lock, complete/fail a merge job, run git merge, or re-register itself as a merge worker/job. Hook guards also block removing the current Codex session worktree, because deleting the session cwd breaks later tool and hook calls.
Cancel a full protocol runtime record by explicit operator intent:
dd-flow protocol cancel PRT-... --reason "replaced by fresh run" --close-sessions true --cancel-queue true --release-locks true --worktree keep --jsonFor a stale disposable feature checkout, use --worktree remove. The command removes the git worktree recorded either in worktree_records or in stable runtime state.workspace; with --force, it also force-deletes the local feature branch when present. It refuses dirty worktrees without --force and refuses to remove the stable project root or the current process working directory.
Cleanup
Use cleanup when local runtime state is stale after a crash, abandoned rerun, or removed feature worktree. cleanup scan is read-only. cleanup apply requires an explicit plan file and reason, reports requested/applied/changed/skipped action results, and dirty/destructive worktree actions require --force --reason. Worktree record cleanup marks a checkout removed only when the path is already absent; if the checkout still exists, the record is closed as kept rather than silently deleting files.
dd-flow cleanup scan --project-root "$PROJECT_ROOT" --json > .tasks/dd-flow-cleanup-scan.json
jq '.plan' .tasks/dd-flow-cleanup-scan.json > .tasks/dd-flow-cleanup-plan.json
dd-flow cleanup apply --project-root "$PROJECT_ROOT" --plan-file .tasks/dd-flow-cleanup-plan.json --reason "clear stale local rerun state" --jsonThe CLI remains mechanical: it cancels or repairs explicit runtime rows, but it does not decide whether a protocol, experiment, merge, or verification was semantically good.
Dashboards
Dashboard markdown is rendered state, not source of truth. Successful state-changing commands refresh project and global dashboards automatically when dashboard.auto_refresh is enabled. dashboard refresh also rewrites both the project dashboard and the global dashboard when they are enabled, so an already-open cmux markdown viewer receives live updates.
The dashboard is intentionally compact: it shows the last update time, active protocols/sessions, merge queue summary, active locks, recent outcomes, and missing project roots without dumping full hook payloads into the main view.
Defaults:
project dashboard: .tasks/dd-flow-dashboard.md
global dashboard: ~/.dd-flow/dashboard.mdUseful commands:
dd-flow dashboard render --project-root "$PROJECT_ROOT" --json
dd-flow dashboard render-global --json
dd-flow dashboard refresh --project-root "$PROJECT_ROOT" --open false --json
dd-flow dashboard open --project-root "$PROJECT_ROOT" --viewer cmux --jsonConfig keys:
dd-flow project config set --project-root "$PROJECT_ROOT" --key dashboard.auto_refresh --value true --json
dd-flow project config set --project-root "$PROJECT_ROOT" --key dashboard.project --value true --json
dd-flow project config set --project-root "$PROJECT_ROOT" --key dashboard.global --value true --json
dd-flow project config set --project-root "$PROJECT_ROOT" --key integrations.cmux.mode --value auto --jsoncmux is a viewer only. Ordinary auto-refresh writes markdown and does not open cmux.
Worktrunk And Codex
dd-flow-cli records Worktrunk-backed worktree state but does not replace Worktrunk as the owner of worktree lifecycle.
dd-flow worktree plan --protocol-id PRT-... --json
dd-flow worktree create --protocol-id PRT-... --branch feature/name --base main --path "$DD_FLOW_HOME/projects/PRJ-001-dd-flow-playground/checkouts/worktrees/PRT-.../repo" --json
dd-flow worktree status --protocol-id PRT-... --jsonworktree plan suggests project-scoped service checkout paths under DD_FLOW_HOME/projects/<PRJ-ID-slug>/checkouts/; it no longer points new work at project-local .tasks/worktrees.
Codex integration is explicit and auditable. Managed Codex homes and hooks are local operational scaffolding; deterministic tests must not mutate the user's default ~/.codex without an explicit target and confirmation.
dd-flow codex home plan --project-root "$PROJECT_ROOT" --json
dd-flow codex hooks status --project-root "$PROJECT_ROOT" --target isolated --json
dd-flow codex hook handle --event Stop --project-root "$PROJECT_ROOT" --jsonVerification
Local verification for this repository:
pnpm test
pnpm typecheck
pnpm lint
pnpm buildManual smoke checks should use a temporary DD_FLOW_HOME when they do not intentionally inspect the operator's real local state.
