plugins/coordinator/skills/consolidate-git/SKILL.md
Cleans up branch sprawl. Triggers: consolidate branches, clean up branches, stale branches, merge all branches.
npx skillsauth add oduffy-delphi/coordinator-claude consolidate-gitInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Reduce branch and worktree sprawl to a single clean workstream branch. Inventories all local and remote branches AND all worktrees, absorbs any unique commits into the current branch, deletes stale branches, and removes stale worktrees (including locked ones whose work has been absorbed).
This skill does NOT merge to main by default. Consolidation and shipping are separate decisions. The goal here is to leave the repo with one current branch holding all of my in-flight work, no leftover sibling branches, no stale worktrees. Whether that branch is ready to ship to main is a separate judgment for /merge-to-main, made after this skill reports. At the end, the EM MAY ask the PM whether to chain into /merge-to-main if the consolidated branch looks merge-ready — but the default exit is "consolidation complete, branch is yours."
Worktrees are not exempt. A worktree whose branch tip is already absorbed into main (or the current branch) is stale state, not active work — the branch lock is a stale signal, not a permanent reservation. The skill removes such worktrees by default. Worktrees with genuine unique commits proceed through the same absorb-or-skip evidence gate as any other branch.
Announce at start: "I'm using the coordinator:consolidate-git skill to consolidate branches and worktrees into the current branch. Merge to main is a separate step."
List all local and remote branches, determine ownership, and categorize.
1a. Identify the current user:
MY_EMAIL=$(git config user.email)
1b. List all branches with tip author:
git branch -a
For each branch (excluding main and the current branch), check the author of the most recent commit on the tip:
# Local branch:
git log -1 --format='%ae' <branch>
# Remote-only branch:
git log -1 --format='%ae' origin/<branch>
1c. Categorize each branch:
| Category | Definition | Action |
|----------|-----------|--------|
| current | The checked-out branch | Absorb target — everything merges here |
| main | The trunk branch | Merge target — current branch merges here at the end |
| mine (stale) | Tip author matches $MY_EMAIL | Check for unique commits, absorb if any, then delete |
| other's | Tip author does NOT match $MY_EMAIL | Leave untouched |
1d. Present the inventory to the PM as a table:
| Branch | Local | Remote | Owner | Category |
|--------|-------|--------|-------|----------|
| main | yes | yes | — | trunk |
| work/striker/2026-03-20 | yes | yes | me | mine (stale) |
| feature/foo | no | yes | me | mine (stale) |
| feature/bar | no | yes | [email protected] | other's — skipped |
| work/striker/2026-03-23 | yes (current) | yes | me | current |
Only branches categorized as "mine (stale)" proceed to Steps 2–5. Other people's branches are reported but never touched.
Enumerate all worktrees and join against the branch inventory. A worktree is just another checkout of a branch — its branch is subject to the same absorb-or-skip logic as any other branch. The only special handling: the worktree directory itself must be removed before the branch can be deleted.
git worktree list --porcelain
For each worktree (excluding the primary .git-owning checkout):
locked line in porcelain output).origin/main OR from the current branch → stale (worktree absorbed) → candidate for removal.Add a Worktrees section to the inventory table:
### Worktrees
| Path | Branch | Locked | Owner | Category |
|------|--------|--------|-------|----------|
| .claude/worktrees/agent-a07… | worktree-agent-a07… | yes | me | stale (absorbed into main) |
| .claude/worktrees/agent-aec… | worktree-agent-aec… | yes | me | stale (absorbed into main) |
Locked is not a veto. A lock from a long-finished isolation run is exactly the state this skill exists to clean up. If the branch is absorbed, the worktree gets removed in Step 5 with --force after the lock is cleared (git worktree unlock <path>). If the worktree has uncommitted changes (git -C <path> status --porcelain non-empty), surface them to the PM before removing — that is genuine in-flight work and warrants pause.
For each stale branch, check if it has commits not in the current branch:
# For local branches:
git log --oneline <current-branch>..<stale-branch>
# For remote-only branches:
git log --oneline <current-branch>..origin/<stale-branch>
Categorize the result:
Do NOT label a branch "superseded," "stale-experiment," "already-applied," or any other verdict at this step. The only categories Step 2 emits are no unique commits and has unique commits — count N. Anything else is a Step 4 verdict and requires Step 3 evidence first.
Hard gate: no verdict may be emitted before this step's output is on the page.
For each branch flagged "has unique commits" in Step 2, run BOTH:
git log --oneline <current-branch>..<stale-branch-or-origin/...>
git show --stat <each-commit-sha>
Then emit an inspection table to the PM before proposing any absorb/skip decision:
### Inspection: <branch-name> (<N> unique commits)
- <sha> <subject> — files: <path1>, <path2> (+X/-Y)
- <sha> <subject> — files: <path1> (+X/-Y)
For commits whose diff is non-trivial (>~20 lines or touches shared infra / plugin internals / configs), also run git show <sha> and quote the load-bearing hunks in 1–3 lines.
Self-check before moving to Step 4 — answer each in one sentence, in the message:
git show --stat (or git show) on every listed sha? (If no: stop, run it.)A "superseded" verdict that names no superseding path on the current branch is a doctrine violation — go back and look.
Only after Step 3's inspection table and self-check are on the page, for each branch:
# Review: patrik F1 — inline override required; cherry-pick from stale branches
# is an off-daily operation caught by block-off-daily-branch.sh.
COORDINATOR_OVERRIDE_BRANCH=1 COORDINATOR_OVERRIDE_BRANCH_REASON="consolidate-git step 3 cherry-pick from <stale-branch>" \
git cherry-pick <commit> --no-edit
COORDINATOR_OVERRIDE_BRANCH=1 COORDINATOR_OVERRIDE_BRANCH_REASON="consolidate-git step 3 merge <stale-branch>" \
git merge <stale-branch> --no-edit
git cherry-pick --abort or git merge --abort) and skip — note this in the reportReport each absorption decision to the PM:
"Branch
work/striker/2026-03-20has 2 unique commits — both are experiment data snapshots already superseded by current branch. Skipping."
or:
"Branch
feature/auth-rewritehas 5 unique commits with real code changes. Cherry-picking into current branch."
After all unique commits are absorbed (or explicitly skipped), remove stale worktrees first (a branch checked out in a worktree cannot be git branch -d'd), then delete the branches.
5a. Remove stale worktrees:
For each worktree categorized as stale in Step 1.5 (and not flagged as having uncommitted changes the PM elected to preserve):
# Clear the lock if present — the lock is leftover isolation-mode state, not a live reservation.
git worktree unlock <path> 2>/dev/null || true
# Remove the worktree. --force is needed because locked/checked-out worktrees
# refuse plain `git worktree remove`. Safe here: we already verified the branch
# tip is reachable from main / current and the working tree is clean (or PM-approved).
git worktree remove --force <path>
After removing all stale worktrees, prune any administrative remnants:
git worktree prune
5b. Delete branches:
# Local branches — use safe delete (-d), not force delete (-D)
git branch -d <branch>
# Remote branches — batch deletions into one push
git push origin --delete <branch1> <branch2> <branch3>
Use -d (safe delete), not -D. Safe delete will refuse if the branch has unmerged commits — this is a final safety net. If -d refuses, investigate before escalating to -D with PM approval.
After deletion, prune stale remote tracking refs:
git fetch --prune
When conflicts were resolved during cherry-pick or merge in Step 4 — especially on shared files (plugin internals, shared scripts, configs) — re-verify that the absorbed changes actually survived.
Why this matters: Last-writer-wins silently reverts edits when both sides touched the same hunk and the conflict was resolved naively. An absorption that "succeeded" may have silently dropped changes from the source branch.
Verification steps:
git show HEAD:<file-path> | grep -F "<canonical phrase from your change>"
~/.claude/ plugin files and project-wide config files touched by multiple branches in the consolidation.## Branch Consolidation Complete
### Absorbed
- `work/striker/2026-03-20` — no unique commits (already merged)
- `feature/foo` — 3 commits cherry-picked into current branch
### Skipped (superseded)
- `work/striker/2026-03-19` — 1 commit (stale experiment data, current branch has newer version)
### Deleted
- Local: work/striker/2026-03-20, work/striker/2026-03-19
- Remote: origin/work/striker/2026-03-20, origin/work/striker/2026-03-19, origin/feature/foo
- Worktrees: .claude/worktrees/agent-a07… (branch absorbed into main), .claude/worktrees/agent-aec… (branch absorbed into main)
### Left Untouched (other owners)
- `feature/bar` ([email protected]) — not ours, skipped
### Current State
- On branch: `work/striker/2026-03-23` @ {sha}
- Ahead of `origin/main` by N commits — not merged (consolidation only)
- All of *my* sibling branches and worktrees absorbed or removed; only main + other owners' branches remain
After reporting, the EM MAY ask the PM whether to chain into /merge-to-main — but only when the consolidated branch looks merge-ready. Default exit is to stop here.
Skip the prompt when:
Offer the prompt when:
/merge-to-main candidate before consolidation started.Phrase as a recommendation, not a question to ratify default behavior:
"Consolidated branch looks merge-ready (N commits ahead of main, all absorbed work passes review). Want me to chain into
/merge-to-main?"
Otherwise: stop. Merge to main is a separate invocation.
If on main with no other branches: Abort early — nothing to consolidate.
If the current branch is behind main: Merge main into the current branch first before absorbing other branches — ensures the final state includes everything.
If a stale branch has diverged significantly: Prefer merge over cherry-pick. If the merge has extensive conflicts, flag to the PM rather than resolving silently — the PM may want to inspect before committing.
If remote branches have no local counterpart: Fetch them first (git fetch origin <branch>) to inspect their commits, then delete the remote after inspection.
/merge-to-main, invoked separately by the PM (optionally suggested by the EM in Step 8 when the branch looks merge-ready).-d (safe) by default. -D only with explicit PM approval.git config user.email are candidates. Everyone else's are reported but never modified or deleted.tools
Orient session — preflight, load context, choose work
documentation
Wrap up finished work — capture lessons, update docs
testing
Use before commit, /merge-to-main, /workday-complete, or to validate repo state. Resolves and runs the project's configured fast-test command.
development
Root-cause discipline for ONE identified bug, test failure, or unexpected behavior — pin the premise, reproduce, trace to source, fix at source, verify. For a single known issue, not a codebase sweep.