WU Prep Workflow
Completing a Work Unit is a two-step process. This guide explains the workflow.
Why Two Steps?
Section titled “Why Two Steps?”The previous single-step wu:done ran gates on main, which could cause issues with worktree isolation. The new workflow:
wu:prepruns gates in the worktree (where your changes are)wu:donedoes merge and cleanup from main
This ensures gates validate your actual changes, not stale main code.
The Workflow
Section titled “The Workflow”-
Work in your worktree
Complete your implementation, write tests, commit changes.
-
Run wu:prep from worktree
This command:
- Validates you’re in a worktree
- Runs quality gates
- Prints a copy-paste instruction for the next step
-
Run wu:done in main (copy-paste)
Copy the command printed by
wu:prep.pnpm -Crunswu:donein the main checkout without moving your shell, and the line is written for your shell:On Windows the path is printed with backslashes and quoted so the same line runs in PowerShell and cmd.exe, for example
pnpm -C "C:\Users\First Last\repo" wu:done --id <wu-id>.This command:
- Validates you’re in main checkout
- Fast-forward merges your changes
- Creates completion stamp
- Runs configured external work-item close-out/readback hooks after successful local completion
- Removes the worktree
If a commit does invalidate the checkpoint, wu:done prints both the prepared commit and the
current commit before rerunning gates. The fallback retains the WU identity and replays named gate
skips recorded at prep. After any intentional fix or notes edit, the normal recovery remains:
commit, rerun wu:prep, then run the printed wu:done command without another commit.
How wu:done reuses the prep checkpoint
Section titled “How wu:done reuses the prep checkpoint”wu:done skips gates when the checkpoint wu:prep wrote is still valid. The key is the normalized
diff hash of your branch against its merge-base with origin/main, within the checkpoint TTL
(gates.checkpoint_reuse_ttl_minutes, default 1440). Main moving is not part of the key: another
WU landing, a metadata-only commit, or a rebase onto unrelated changes still reuses the checkpoint.
wu:done refuses reuse, and prints the overlapping paths, only when files that changed on main since
prep overlap a file your branch touches or a declared gate input (tests.*, scoped test paths,
pnpm-lock.yaml, workspace.yaml). A wu:done retry after a failed landing on an identical diff
reuses the passing evidence of its own earlier gate run. Every decision (reused, refused with a
reason code, or none when no checkpoint existed) is recorded in the local flow log so the reuse
rate is measurable.
When another WU lands a change to a file yours touches while wu:done is running its gates, wu:done
rebases onto the new target and re-runs the gates on the rebased tree before it writes the done commit
(up to three rounds). Its own metadata commit, the ledger, and changes to files your WU does not touch never
trigger a re-run. --no-auto-rebase opts out.
Landing Playbook
Section titled “Landing Playbook”| Refusal or task | Sanctioned recovery |
|---|---|
| Generated manifest or metadata is dirty | Run the repository formatter, review the generated diff, and commit it before prep. |
--allow-todo has no notes justification | Run pnpm wu:edit --id <wu-id> --notes "allow-todo: <justification>"; the edit creates a commit, so rerun prep afterward. |
| Several external PBIs/work items exist | Cover every declared item in acceptance criteria. Leave configured provider close-out and readback to successful wu:done. |
| Contract completeness fails | Update the complete named surface: schema, manifest or registry, implementation, tests, and affected docs. |
| The worktree or main checkout is dirty | Inspect status, commit only intended worktree changes, preserve unrelated user changes, and follow the governed recovery printed by the command. |
| An attestation or artifact is stale | Rerun its documented producer against the current commit; do not hand-edit its JSON or skip an unrelated producing gate. |
| Prep succeeds | Treat prep and pnpm -C <main> wu:done --id <wu-id> as one uninterrupted landing operation. |
Command Reference
Section titled “Command Reference”wu:prep
Section titled “wu:prep”Prepares a WU for completion by running gates in the worktree.
| Flag | Description |
|---|---|
--id | WU ID (required) |
--docs-only | Run docs-only gates (format, spec-linter) |
--full-tests | Disable tests.unit scoping and run the default incremental/full test gate behavior |
When the active preset supports path-scoped execution, wu:prep uses the current WU’s
tests.unit entries to scope the test gate.
Safe prep-evidence reuse
Section titled “Safe prep-evidence reuse”Prep-evidence reuse is default-on. Opt out with:
Only an identical eligible low- or medium-risk plan can reuse passed evidence. Missing, malformed,
stale, cross-workspace, or mismatched evidence runs gates fresh. --full-tests and
high-risk prep run gates fresh; --full-tests also forces full test execution. report_all
(default true) does not exclude reuse — it is already one of the hashed execution-plan inputs.
Snapshot isolation remains OFF: reuse does not pin or execute an immutable workspace snapshot.
For presets that do not support path-scoped execution, such as dotnet, wu:prep falls back to
the configured default test gate flow instead of trying a JavaScript-specific scoped runner.
Sensitive-data evidence is committed by wu:prep
Section titled “Sensitive-data evidence is committed by wu:prep”When a WU’s code_paths include a sensitive-data candidate (a path segment such as auth,
credentials, pii or user-data), wu:prep evaluates the branch diff and records the decision
on the WU record as escalation_evidence. It commits that change as
chore(<wu>): record wu:prep sensitive-data evidence before the gates run. The worktree is clean
when prep returns, so wu:done needs no manual commit in between.
escalation_triggersis set atwu:claim, so a sensitive path added later (awu:editthat widenscode_paths, which a claimed WU records as a pending edit untilwu:done) is seen only here.wu:prepevaluates the effectivecode_pathsand, when a decision escalates, addssensitive_datatoescalation_triggersin the same commit, sowu:escalateandwu:statusshow it.wu:donealso fails closed on any recorded escalating decision, so the landing needswu:escalate --resolveeven if the trigger were missing. A WU whose every decision is diff evidence with no escalation stays cleared.wu:editon a ready WU adds the trigger at edit time.- A re-run over identical evidence changes nothing and makes no commit. A WU with no sensitive-data candidate records nothing and commits nothing.
- The commit holds the WU record and the lifecycle ledger only. If the record has any other
uncommitted change,
wu:prepstops before the gates and names it: commit or revert that change, then rerun. - A handoff receipt presented to
wu:prepis bound to the committed branch diff, which this commit changes.wu:prepre-checks the receipt right after the commit and stops before the gates if it no longer matches. Runwu:briefagain from the claim-holder session, then rerunwu:prep; the rerun commits nothing. A claim-holder or same-user run uses no receipt. - Human resolution still applies. A sensitive-data claim sets
requires_human_escalation, and that flag is always respected, so resolve it as described under Escalation Resolution beforewu:done.
Scope Coverage and Route Directories
Section titled “Scope Coverage and Route Directories”wu:prep verifies that every declared code_paths entry covers at least one changed file. Replace
a complete scope with explicit repeatable values:
Bracketed framework route segments such as [orgId] are matched literally when that segment exists
in the changed path. Ordinary glob character classes keep their normal meaning, so [ab] still
matches either a or b when no literal [ab] segment is present.
The same matcher is authoritative during wu:claim overlap checks and wu:done completion
validation. Claim also completes all blocking overlap, mode, and branch checks before it records an
ownership advisory or persists claim state, a session, a worktree, or a branch. A rejected
preflight therefore does not leave a claim or ownership-advisory event behind; a validated claim
records its applicable ownership advisory exactly once, inside the same commit as the claim itself.
Claim cost: one commit, one push
Section titled “Claim cost: one commit, one push”From wu:claim to your first edit, the shared origin main receives exactly one commit and one
push. The claim commit carries the WU record, the generated backlog and status views, the claim
event, and (for an initiative WU that was not delegated to you) the ownership-advisory audit
event. wu:brief records its evidence on your lane branch, not on main, so it adds nothing to
main. If another checkout pushed to main first, the claim is regenerated from the new tip and
pushed again (up to eight attempts with jittered back-off) rather than rebased, because two claims
rewrite the same generated backlog lines. Claiming different WUs from two machines therefore both
succeed, and claiming the same WU yields exactly one winner.
When software_delivery.gates.delivery_review.enabled: true, wu:prep can also run the native
delivery_review gate. Auto-run is controlled globally by
software_delivery.gates.delivery_review.auto_run.
Must be run from worktree - errors if run from main.
Native Delivery Review During wu:prep
Section titled “Native Delivery Review During wu:prep”delivery_review is a Software Delivery pack feature, not a vendor overlay. Enforcement is
registered from the global gate config and behaves the same for Claude, Codex, Cursor, Windsurf,
Cline, Aider, custom agents, and hosted cloud workers.
wu:prep auto-runs delivery_review only when:
software_delivery.gates.delivery_review.enabledistruesoftware_delivery.gates.delivery_review.auto_runistrue- the WU type does not match
software_delivery.gates.delivery_review.skip_types
Direct gate execution with pnpm gates uses the same global config, but does not require
auto_run: true.
wu:prep records the ids of the gates it executed, and a digest of the gate configuration that
selected them, in its checkpoint. wu:done reuses the checkpoint only when every gate it requires was
executed and the digest is unchanged (refusal code gate-set-mismatch). With auto_run: false,
prep does not run delivery_review, so wu:done runs it instead of skipping it; with
auto_run: true the checkpoint is reused as before.
When a verifier_command is configured, software_delivery.gates.delivery_review.verifier_command_mode
(default always) decides whether wu:prep runs it unconditionally or only when native evidence
is insufficient (on-insufficient-evidence) — see the
Gates Reference for the full semantics.
Legacy client-scoped software_delivery.agents.clients.<client>.features.delivery_review blocks
are accepted for one release and emit a migration warning. They may still be used for adapter UX or
prompt hints, but they are not the core enforcement switch.
The review writes a stable artifact at delivery-review/<wu-id>.json in the workspace artifacts with a
portable verdict contract:
PASSpasses this evidence gate; it does not certify semantic correctnessPARTIALwarns by default, and blocks whendelivery_review.block_partialistrueFAILblockswu:prep
The result is designed for any host or product to consume later. It does not require
a hosted control plane, does not assume a specific agent vendor, and does not rename or overload
wu:verify. See the Gates Reference for the
catalogue entry, skippability default, and artifact contract.
wu:done
Section titled “wu:done”Completes a WU by merging and cleaning up.
| Flag | Description |
|---|---|
--id | WU ID (required) |
--skip-gate <name> | Skip one explicitly named skippable gate (repeatable; requires —reason and —fix-wu) |
Must be run from main - errors if run from worktree.
Governed rebase recovery
Section titled “Governed rebase recovery”When rebasing combines append-only state conflicts with a conflict in the active WU spec,
wu:done applies the append-only resolver, then invokes the governed metadata resolver for any
remaining paths before checking the index. It follows the same order after each git rebase --continue. The metadata resolver is limited to governed files, including the active WU spec;
source conflicts, another WU’s spec, malformed metadata, and unresolved conflict artifacts still
refuse completion.
If a completion attempt stops after its worktree recorded done, keep that worktree and its
evidence, then rerun the ordinary pnpm wu:done --id <wu-id> recovery route. Recovery accepts only
the same claim generation with an in_progress canonical WU and event ledger paired with a done
worktree WU and event ledger. The authenticated session, owner, branch, registered worktree, and
path must still match. Inconsistent document and event pairs, generic binding calls, and terminal
readback remain strict.
During recovery, the materialized worktree WU YAML supplies lifecycle status.
A pending specification snapshot still supplies effective spec edits such as
title and code_paths, but its status cannot manufacture a recovery state
or replace a materialized done status. The reader leaves the pending snapshot
and ledger unchanged, and the existing claim attestation and document/event
pair checks still decide whether recovery is allowed.
For an unpublished local completion that wu:done identifies as a zombie, the
configured main-sync check runs before the recovery squash. The retry status is
prepared in memory and persisted only by the normal metadata transaction with
its matching event projection. If sync or pre-commit work fails, the preserved
document and ledger remain paired; after a successful squash, retry rollback
uses a snapshot taken from the post-squash pair. Ordinary completion attempts
keep their original pre-write rollback snapshot.
If a later brief or checkpoint commit follows the unpublished completion,
wu:done proves that exact completion and replays each descendant commit onto
its active parent in an isolated candidate. It preserves later source and event
changes, removing only the completion event bound to the old tuple. A remote
completion, ambiguous history, replay conflict, dirty worktree, or changed lane
ref stops recovery without moving the caller’s branch. After replay, the command
rereads the complete active WU document and replays its pending spec entries over
it (each entry adds only its declared, source-allowed fields) before
creating a fresh completion commit. The strict done validator remains in force.
wu:done keeps the shared merge lock for the landing only (rebase, fast-forward merge, push and the
completion stamp); everything else runs before it or after it is released:
- Cleanup never waits for the lock. Automatic state cleanup (
cleanup.trigger: on_done) cleans ignored signals and memory in place and only touches the lock when tracked events need archiving, then tries it once without waiting. A held lock printscleanup=skipped reason=merge-lock-heldand the next landing does the work. - No worktree rebuild in the landing. The metadata commit’s pre-commit hook uses the main checkout’s CLI build, so a WU that changes the CLI no longer rebuilds its own worktree dist inside the lock.
- Post-land sweep. After the lock is released,
wu:donestartspnpm main:sweep --post-landdetached and does not wait for it or for its result; the sweep reports through its own signals. - Archived done logs stay readable.
.logs/wu-done-archive/<WU>/gates-latest.logalways points at the archived log next to it.
External QA / Work-Item Close-Out
Section titled “External QA / Work-Item Close-Out”External systems are side effects, so LumenFlow treats them as lifecycle hooks instead of worker
instructions. A worker may see external_work_items in wu:brief, but those items are context
only. The safe sequence is:
wu:claimwu:brief- implementation
- verification /
wu:prep - local
wu:done - external close-out adapter
- readback adapter
- audit trail
If wu:prep fails, wu:done fails, or branch-pr mode only creates a PR, close-out does not run.
When it does run, audit events are appended to
the external-closeout.ndjson telemetry log.
Configure providers through config:set:
Adapters may be command or api. Template variables available to adapters are {{wu_id}},
{{provider_id}}, {{item_id}}, {{desired_status}}, and {{lifecycle_intent}}.
Error Messages
Section titled “Error Messages”Migration Notes
Section titled “Migration Notes”Before two-step completion
Section titled “Before two-step completion”After two-step completion
Section titled “After two-step completion”Escalation Resolution
Section titled “Escalation Resolution”If a WU has escalation_triggers (e.g., sensitive_data, security_p0), wu:done blocks until the escalation is resolved. Use wu:escalate to check status or resolve:
Locally, human escalation raises the cost of self-resolution and leaves an audit trail. It is not an enforced boundary against an agent that runs with your own user authority.
wu:escalate --resolve refuses when it detects an agent session in the reviewing process, and it needs
--approval-id naming a human-escalation approval. approval:request records the requester from the
checkout’s git user.email (--requester-id cannot assert another identity) together with the requester’s
agent-session evidence, and approval:review records the reviewer. The chain runs wu:escalate, then records
the approval approved (locally and on the control plane), then runs wu:done, because wu:done refuses a
pending approval; a re-run after a failed wu:done does not record it twice. approval:request --context
cannot set the reserved requester_agent key. The resolver is always the checkout’s git user.email
(--resolver is a cross-check only).
Separation rule: a review is refused when the reviewing process shows an agent marker or the requester’s agent session id. The email comparison is an additional refusal only when the requester had no agent-session evidence, so a human reviewing an agent’s request on the same git email passes (the single-identity developer case), and a human-requested approval reviewed by the same email does not.
The WU records escalation_resolution_evidence (approval id, requester, source, and a sha256 digest binding
them to the resolver and time). A request file alone is never accepted. At wu:done the approval is
re-verified: a local approval record must be approved, belong to this WU, have type human-escalation, and
have a reviewer who is a user and not the requester (unless the requester had agent evidence). A
control-plane approval (one posted by approval:request to the control plane, recorded with
source: control-plane) is fetched by id and must show status approved, this WU, type human-escalation and
a user reviewer. An unreachable or missing approval is refused. A resolution that omits
escalation_resolved_triggers is never current. A local approval record must be present in the checkout where
wu:done runs.
--resolve is idempotent. After wu:release --keep-branch, wu:recover --action reset, or a
re-claim, run the same resolve command again. LumenFlow reads the active worktree’s WU as the
completion authority and reconciles an existing resolution without creating an empty commit. No
force flag or manual YAML edit is required.
For a claimed worktree WU, the resolution is committed on the lane branch and published to
origin/<lane-branch>. origin/main receives it when wu:done lands the lane. If another
checkout moved the remote lane branch, the resolution commit is replayed onto the new tip and
pushed again. If every attempt loses, the command fails and names the rerun. A rerun publishes the
existing resolution instead of resolving again. If the local lane was rewritten (for example by
wu:rebase, which does not push a worktree-mode lane), nothing is force-pushed. The resolution
stays committed on the local lane branch, and wu:done lands it. For a WU without a claimed
worktree, the resolution is published to origin/main through the governed micro-worktree push,
which rebases and retries. A rerun checks the origin/main copy and publishes an earlier
resolution that never reached it.
wu:done evaluates the approval gate against the same WU document it reports reading, the
worktree copy. A resolution recorded there passes, even when main’s copy or an earlier pending
wu:edit or wu:rebase snapshot lacks it.
| Flag | Description |
|---|---|
--id | WU ID (required) |
--resolve | Resolve the escalation (refused in an agent session, except with --delegated) |
--approval-id | The human-escalation approval to resolve under (required with --resolve unless --delegated) |
--resolver | Cross-check only: must equal this checkout’s git user.email |
--delegated | Resolve as a different agent seat, only when the trusted escalation.mode is delegated (see below) |
After resolving, wu:done will succeed for escalation-triggered WUs.
Delegated and advisory escalation
Section titled “Delegated and advisory escalation”Who resolves an escalation is a choice in the trusted policy,
software_delivery.escalation.mode: human (the default flow above), delegated (an independent agent
seat runs pnpm wu:escalate --id <wu-id> --resolve --delegated after a zero-findings review and green gates on
the exact tip; it is recorded as delegated-agent, never as a human, and a commit after the
review invalidates it) or off (triggers are recorded but never gate wu:done; agents decide and the landed
WU records escalation_advisory). Delegated resolution is separation of duties plus independent review, and
like the human route it is advisory against an agent running with your own user authority. See
Escalation modes to enable a mode.
Can I skip wu:prep?
Section titled “Can I skip wu:prep?”No. If you run wu:done from main without running wu:prep first, gates won’t have run in the worktree where your changes are. The previous behavior of running gates on main has been removed.
What if gates fail in wu:prep?
Section titled “What if gates fail in wu:prep?”Fix the issues in your worktree, then run wu:prep again. The workflow prevents partial completions.
Do I still need --full-tests for .NET repos?
Section titled “Do I still need --full-tests for .NET repos?”Usually no. For presets such as dotnet, wu:prep already falls back to the configured default
test flow when path-scoped execution is unsupported. Use --full-tests when you want to force the
default incremental/full flow even for presets that support scoped execution.
Does this change named gate skips?
Section titled “Does this change named gate skips?”Named --skip-gate <name> requests remain available for explicitly skippable gates with matching structured failure evidence on main. They require --reason and --fix-wu.
What if gates fail on code I didn’t change?
Section titled “What if gates fail on code I didn’t change?”wu:prep probes the named gate against main. When structured evidence proves the same failure is pre-existing, you may request that specific skippable gate:
New failures introduced by your changes remain blocking and must be fixed first. See Troubleshooting: Pre-existing gate failures for the full verification workflow.