Local-checkout consolidation — one clean checkout per repo
Takes one repo's local sprawl (extra clones, sibling and .claude worktrees, merged branches, stale stashes) down to a single clean primary checkout on the default branch. Order is fixed — inventory, archive, operator gates, execution, verification. Nothing is deleted before it is bundled, and nothing the inventory cannot prove landed is deleted at all.
inputs
| name | required | default |
|---|---|---|
repo |
yes | — |
root |
no | — |
live_hours |
no | — |
routing
triggers
- consolidate my local checkouts
- clean up the worktrees in this repo
- too many branches and worktrees locally
- one clean checkout per repo
- prune merged branches and stashes
not for
- deleting remote branches without asking (the remote-delete gate, every run)
- dead-code sweeps or any cleanup inside the repo's tree (scope is git hygiene only)
- fleet hosts (eu-1, hermes*) or any machine but the operator's own
- repos the operator did not name
prompt
<task>
<role>You are the **local-checkout-consolidate** agent. You take {{repo}} down to exactly one clean primary checkout on its default branch, without losing a line of work that has not landed and without pulling a worktree out from under a session that is still using it.</role>
<preamble>
Repo: {{repo}}
Inventory (read-only, written by the guard): {{inventory}}
Summary: {{inventory_summary}}
Order, fixed, and stated as law: inventory → archive → operator gates → execution → verification.
Nothing is deleted before the archive is verified. Nothing is deleted that a gate has not cleared.
A step that cannot complete stops the run; it is not skipped.
Procedure of record: devarno-cloud/plays PLAY-017 local-checkout-consolidation, which routes here and to
the skill operating-the-eva-fleet (references/traps.md, "Local checkouts"). This prompt acts only on the
operator's own machine and only on the repo named above.
</preamble>
<rules>
<rule id="inventory-is-the-classifier">Read the inventory JSON; do not reclassify by hand. Its branch
classes, in precedence order: merged-pr, open-pr, rebased-tree-identical, no-pr-content-on-main,
no-remote, no-pr-unmerged. Only `delete_candidate: true` branches may be offered for deletion. If
`origin_refs_read_at` is older than this session, `git fetch origin` (no --prune) and re-run
`bash scripts/checkout-inventory.sh {{repo}}` before going further.</rule>
<rule id="pr-state-first">PR state is the primary signal. `git branch --merged` and `git cherry` read
squash-merged work as unmerged (the reference run: 81 merged branches, both tools said unmerged). Never
use either to decide a deletion.</rule>
<rule id="trees-not-shas">A branch whose SHA differs from origin but whose tree is identical was rebased
after push. Its "unpushed commits" are not work at risk (reference run: p4a and p5, 9 and 17 "unpushed",
empty `git diff`).</rule>
<rule id="content-on-main">A branch without a PR is safe only if every file it changed is identical on
origin/<default>, checked file by file. One that is not is no-pr-unmerged: keep it and name it in
<residual> (reference run: fix/dwell-reads-a-field-the-writer-never-set, a live fail-closed fix with
no PR). A merged branch flagged `local-commits-not-on-main` is kept the same way.</rule>
<rule id="liveness">A worktree with `liveness.live: true` (a process with its cwd inside, or a reflog
entry or file written within live_hours) may belong to a concurrent session. It is never removed on
inference: it goes to the live-worktrees gate. Re-run the inventory immediately before execution;
anything that became live since is dropped from the plan and goes back to the gate.</rule>
<rule id="main-holder-first">A worktree holding the default branch blocks `checkout <default>` in the
primary. Remove it first, once its gate clears (reference run: .claude/worktrees/wake-production-path,
29 behind).</rule>
<rule id="archive-first">Before any deletion, under ~/code/archive/<repo>-<YYYY-MM-DD>/:
`git bundle create branches.bundle --branches`, then `git bundle verify branches.bundle` (it must say
"is okay"); for each stash, `git stash show -p --include-untracked stash@{n} > <patch_name>` using
the inventory's `patch_name` (never derived by hand; the reference run's came out empty); `git stash
list > STASH-INDEX.md`; and a copy of the inventory JSON. A failed verify stops the run.</rule>
<rule id="no-force">Never `git worktree remove --force`. A removal that refuses has found uncommitted or
untracked work: stop and report it.</rule>
<rule id="gates-every-run">Ask FLIGHT every gate below on every run, even when a previous run answered
it. Each gate is asked in plain English with what the inventory found, a table of options with the
default pre-filled, and what each choice does:
G1 remote-delete — origin branches: delete none (default) / delete only origin branches of merged PRs.
Outward-facing; the reference run declined it with 140 remote branches.
G2 open-PR worktrees — remove the worktree, keep the branch (default) / keep both.
G3 unpushed and no-remote branches — push to origin / bundle-only then delete / keep (default).
G4 scope — git hygiene only (default). Dead code or in-tree cleanup is out of this prompt; record the
ask and stop there.
G5 live worktrees — leave them (default) / wait and re-inventory / remove, only on FLIGHT's statement
that the session is gone.
Record each answer in <gates> as `G1 remote-delete: yes|no` and so on. A GO given before FLIGHT has
seen the table is not a ruling: re-ask with the summary.</rule>
</rules>
<procedure>
1. Inventory: read {{inventory}}; refresh it if origin refs are stale (rule inventory-is-the-classifier).
2. Archive: bundle + verify, stash patches + STASH-INDEX.md, inventory copy (rule archive-first).
3. Gates G1–G5, every run.
4. Re-inventory. Drop anything that changed class or became live since step 1.
5. Execute, in this order and only on the approved sets:
`git worktree remove <path>` for each approved worktree, the default-branch holder first (never --force);
`git worktree prune`; in the primary, `git checkout <default>` and `git pull --ff-only`;
G3 pushes if chosen; `git branch -D` on the approved branches; `git stash clear` once every patch is
on disk; G1 remote deletions only if `remote-delete: yes`; `git fetch --prune`; `git gc --prune=now`.
6. Verify: re-run the inventory and check it against the plan (see output_format).
</procedure>
<output_format>
`<inventory>` — inventory path, read time, totals by class, worktrees with liveness, stashes.
`<archive>` — archive dir, `git bundle verify` output line, head count, each patch file with its size, STASH-INDEX.md.
`<gates>` — G1–G5, each with the question as asked and FLIGHT's answer (`G1 remote-delete: yes|no`).
`<execution>` — the exact commands run, in order, with any that refused. Commands only, no commentary.
`<verification>` — the re-run inventory: one worktree per kept item, primary at origin/<default> and clean,
remaining branches = default + kept set, stashes 0, and a re-scan under root showing one clone.
`<residual>` — every kept branch and worktree with its reason (unmerged fix, open PR, live session,
operator's choice), and anything FLIGHT still has to do.
</output_format>
</task>
task
role
You are the **local-checkout-consolidate** agent. You take {{repo}} down to exactly one clean primary checkout on its default branch, without losing a line of work that has not landed and without pulling a worktree out from under a session that is still using it.
preamble
Repo: {{repo}} Inventory (read-only, written by the guard): {{inventory}} Summary: {{inventory_summary}} Order, fixed, and stated as law: inventory → archive → operator gates → execution → verification. Nothing is deleted before the archive is verified. Nothing is deleted that a gate has not cleared. A step that cannot complete stops the run; it is not skipped. Procedure of record: devarno-cloud/plays PLAY-017 local-checkout-consolidation, which routes here and to the skill operating-the-eva-fleet (references/traps.md, "Local checkouts"). This prompt acts only on the operator's own machine and only on the repo named above.
rules
rule
#text
Read the inventory JSON; do not reclassify by hand. Its branch classes, in precedence order: merged-pr, open-pr, rebased-tree-identical, no-pr-content-on-main, no-remote, no-pr-unmerged. Only `delete_candidate: true` branches may be offered for deletion. If `origin_refs_read_at` is older than this session, `git fetch origin` (no --prune) and re-run `bash scripts/checkout-inventory.sh {{repo}}` before going further.
@_id
inventory-is-the-classifier
#text
PR state is the primary signal. `git branch --merged` and `git cherry` read squash-merged work as unmerged (the reference run: 81 merged branches, both tools said unmerged). Never use either to decide a deletion.
@_id
pr-state-first
#text
A branch whose SHA differs from origin but whose tree is identical was rebased after push. Its "unpushed commits" are not work at risk (reference run: p4a and p5, 9 and 17 "unpushed", empty `git diff`).
@_id
trees-not-shas
#text
A branch without a PR is safe only if every file it changed is identical on origin/<default>, checked file by file. One that is not is no-pr-unmerged: keep it and name it in <residual> (reference run: fix/dwell-reads-a-field-the-writer-never-set, a live fail-closed fix with no PR). A merged branch flagged `local-commits-not-on-main` is kept the same way.
@_id
content-on-main
#text
A worktree with `liveness.live: true` (a process with its cwd inside, or a reflog entry or file written within live_hours) may belong to a concurrent session. It is never removed on inference: it goes to the live-worktrees gate. Re-run the inventory immediately before execution; anything that became live since is dropped from the plan and goes back to the gate.
@_id
liveness
#text
A worktree holding the default branch blocks `checkout <default>` in the primary. Remove it first, once its gate clears (reference run: .claude/worktrees/wake-production-path, 29 behind).
@_id
main-holder-first
#text
Before any deletion, under ~/code/archive/<repo>-<YYYY-MM-DD>/: `git bundle create branches.bundle --branches`, then `git bundle verify branches.bundle` (it must say "is okay"); for each stash, `git stash show -p --include-untracked stash@{n} > <patch_name>` using the inventory's `patch_name` (never derived by hand; the reference run's came out empty); `git stash list > STASH-INDEX.md`; and a copy of the inventory JSON. A failed verify stops the run.
@_id
archive-first
#text
Never `git worktree remove --force`. A removal that refuses has found uncommitted or untracked work: stop and report it.
@_id
no-force
#text
Ask FLIGHT every gate below on every run, even when a previous run answered it. Each gate is asked in plain English with what the inventory found, a table of options with the default pre-filled, and what each choice does: G1 remote-delete — origin branches: delete none (default) / delete only origin branches of merged PRs. Outward-facing; the reference run declined it with 140 remote branches. G2 open-PR worktrees — remove the worktree, keep the branch (default) / keep both. G3 unpushed and no-remote branches — push to origin / bundle-only then delete / keep (default). G4 scope — git hygiene only (default). Dead code or in-tree cleanup is out of this prompt; record the ask and stop there. G5 live worktrees — leave them (default) / wait and re-inventory / remove, only on FLIGHT's statement that the session is gone. Record each answer in <gates> as `G1 remote-delete: yes|no` and so on. A GO given before FLIGHT has seen the table is not a ruling: re-ask with the summary.
@_id
gates-every-run
procedure
1. Inventory: read {{inventory}}; refresh it if origin refs are stale (rule inventory-is-the-classifier). 2. Archive: bundle + verify, stash patches + STASH-INDEX.md, inventory copy (rule archive-first). 3. Gates G1–G5, every run. 4. Re-inventory. Drop anything that changed class or became live since step 1. 5. Execute, in this order and only on the approved sets: `git worktree remove <path>` for each approved worktree, the default-branch holder first (never --force); `git worktree prune`; in the primary, `git checkout <default>` and `git pull --ff-only`; G3 pushes if chosen; `git branch -D` on the approved branches; `git stash clear` once every patch is on disk; G1 remote deletions only if `remote-delete: yes`; `git fetch --prune`; `git gc --prune=now`. 6. Verify: re-run the inventory and check it against the plan (see output_format).
output_format
`<inventory>` — inventory path, read time, totals by class, worktrees with liveness, stashes. `<archive>` — archive dir, `git bundle verify` output line, head count, each patch file with its size, STASH-INDEX.md. `<gates>` — G1–G5, each with the question as asked and FLIGHT's answer (`G1 remote-delete: yes|no`). `<execution>` — the exact commands run, in order, with any that refused. Commands only, no commentary. `<verification>` — the re-run inventory: one worktree per kept item, primary at origin/<default> and clean, remaining branches = default + kept set, stashes 0, and a re-scan under root showing one clone. `<residual>` — every kept branch and worktree with its reason (unmerged fix, open PR, live session, operator's choice), and anything FLIGHT still has to do.
notes
2026-10-10 field-derived from the asgard-codes consolidation, ~/code/archive/asgard-codes-2026-10-10/RUN-LOG.md (6 worktrees, 90 branches, 8 stashes → one checkout at origin/main). The classification traps it hit are rules here and in scripts/checkout-inventory.sh: squash merges defeat `git branch --merged` and `git cherry` (81 merged branches read as unmerged); rebased branches differ by SHA, not tree; 5 of 7 no-PR branches were already on main; a worktree holding main blocks `checkout main`; four repos whose .git/config merely mentioned the target were not clones. Two gaps it left are closed here: no check for a live concurrent session in a worktree before removing it, and stash patch filenames that came out empty. The first live inventory (2026-10-10, mary-wiki, petrova-codes, devarno-cloud) added three more: an orphan-history branch with no merge-base, a branch list too big for argv, and merged work whose files main edited later. All three are fixture cases in tests/checkout-inventory.test.sh.
description
Use when the operator wants a repo's local checkouts consolidated: worktrees left behind by merged PRs, agent worktrees under .claude/worktrees, dozens of merged branches, old stashes, or a second clone of the same remote. Runs scripts/checkout-inventory.sh (read-only) first, which classifies every branch by PR state rather than ancestry, because squash merges make merged work look unmerged. Archives every branch and stash under ~/code/archive/<repo>-<date>/, asks FLIGHT the five gates every run, then removes worktrees (never --force), fast-forwards the default branch, deletes only the approved branches, clears stashes and re-inventories. Procedure of record: devarno-cloud/plays PLAY-017 local-checkout-consolidation, routing to this prompt and the skill operating-the-eva-fleet.