tidy
Use when every repository in the workspace must end synced and clean: each one on its default branch, everything pushed, everything pulled, no leftover branches or worktrees. Covers the root repo and
SKILL.mdskills/tidy/SKILL.md
GitHubtidy — every repo on main, pushed, pulled, nothing left over
One request, every repository in the workspace. When it ends, each repo shows the same picture:
- the main checkout is on the default branch, up to date with the remote;
- nothing local is missing from the remote — no unpushed commit, on any branch;
- no worktree and no branch whose work has already landed, locally or on the remote.
The job is cleanup, and cleanup is where work gets lost. So the rule under every step: delete only what is proven to be in the default branch already. Anything else is pushed, kept and reported — never thrown away to make the picture look clean.
1. Find the repositories
The workspace root, plus every nested git repository below it (child projects keep their own git):
git rev-parse --show-toplevel # the root repo, if any
find . -name .git -maxdepth 4 -not -path '*/node_modules/*' -not -path '*/.worktrees/*'
A .git that is a file is a linked worktree or a submodule, not a repo of its own: skip it here,
it is handled from its parent in step 4. Name the list in one line before touching anything.
Never change the working directory into a child repo. Use git -C <repo> and absolute paths.
Session hooks take the cwd as the project, and a cd plants state inside the child.
2. Read everything before changing anything
Per repo, one pass, no writes except the fetch:
git -C <repo> fetch --all --prune --quiet
git -C <repo> symbolic-ref --short refs/remotes/origin/HEAD # the default branch (origin/main)
git -C <repo> status --short --branch # current branch, ahead/behind, dirty
git -C <repo> for-each-ref --format='%(refname:short) %(upstream:short) %(upstream:track)' refs/heads
git -C <repo> worktree list --porcelain
git -C <repo> stash list
gh pr list --state open --json number,headRefName 2>/dev/null # open PRs, when gh is there
No remote at all → the repo cannot be "pushed"; report it and only tidy what is local.
3. Classify every item
Each branch, worktree and remote branch lands in exactly one row. Decide by the commands, not by reading names.
| Item | Proof | Action |
|---|---|---|
| Worktree whose work landed | npx @ericrisco/rsc worktrees calls it safe |
npx @ericrisco/rsc worktrees reap |
| Worktree whose directory is gone | prunable in the porcelain list |
git -C <repo> worktree prune |
| Worktree with live or unsaved work | anything the reaper refuses | keep; report the reaper's reason |
| Local branch merged into the default | merge-base --is-ancestor <b> origin/<default> |
git branch -d <b> |
| Local branch squash-merged | its content is in the default (below) | git branch -D <b>, tip SHA in the report |
| Local branch with unlanded commits | neither of the two above | push it (git push -u origin <b>), keep it |
| Remote branch whose content landed | same content check, against origin/<b> |
delete it, after one confirmation (step 6) |
| Remote branch with an open PR | gh pr list |
keep; it is live |
| Remote branch unlanded, no PR | not in the default, no PR | keep; report it as an open question |
| Stash | any | keep; report it. Never stash drop without a yes |
The content check (what makes a squash merge count as landed): merge the branch into the default in memory and see whether anything changes.
git -C <repo> merge-tree --write-tree origin/<default> <branch> | head -1 # resulting tree
git -C <repo> rev-parse origin/<default>^{tree} # default's tree
Same tree → the branch adds nothing; it has landed. A conflict, a different tree, or a git older
than 2.38 (no --write-tree) → not proven, so it is kept. A branch that never carried a commit
of its own is a live workspace someone just opened, not leftovers: keep it.
Never delete the default branch, the current branch of another checkout, or rsc/knowledge.
4. Act, in this order
- Uncommitted changes stop that repo. Show the files and ask once: commit them, stash them, or leave the repo as is. Never commit or discard someone's work in progress on your own. Another session working in that checkout (the isolation notice says so) → do not touch its branch or its files at all; report it.
- Push what is only local. Every branch with unlanded commits goes to the remote. The default
branch ahead of
origin: push it if the default branch is open here; if it is protected (branches and PRs), move those commits to a branch and open a PR — ask first. - Retire what landed.
npx @ericrisco/rsc worktrees reapfor worktrees (it refuses anything that would lose a file, and its refusal stands),worktree prune, then the branches from step 3. Without rsc in that repo, follow the manual path in../worktrees/SKILL.md. - Back to the default and pull. Clean checkout on another branch whose work is pushed →
git -C <repo> switch <default>. Thengit -C <repo> pull --ff-only. A pull that cannot fast-forward means the local default diverged: stop and report, neverreset --hard.
5. What is never done
reset --hard,clean -fdx,push --force,worktree remove --force,stash droporclear— not to make the picture tidy, not on a "probably fine".- Deleting a branch or worktree because its name looks old. Only the proofs in step 3 count.
- Committing straight to a protected default branch.
6. Remote branches: one confirmation, for all of them
Deleting a remote branch reaches everyone who clones the repo. Collect every remote branch whose content landed, across all repos, and ask once with the full list:
Remote branches already in main, to delete:
rsc-harness: feat/x, fix/y·rsc-brain: feat/z. ¿Las borro? (sí/no)
On yes: git -C <repo> push origin --delete <b> .... On no: keep them and say so in the report.
7. The report
One table, one row per repo, then the open questions. Only facts you observed in this run.
| Repo | Branch | ↑ / ↓ | Worktrees | Local branches | Remote branches | Pending |
|---|---|---|---|---|---|---|
| rsc-harness | main | 0 / 0 | 0 | 0 | 2 deleted | — |
| rsc-brain | main | 0 / 0 | 1 kept | 1 pushed | — | feat/z: live, no PR |
Under it: every deleted squash-merged branch with its tip SHA (it can be brought back with
git branch <name> <sha>), and every item kept with the one action that would close it. A repo that
is not fully clean is reported as not clean, with the reason; a tidy-looking summary that hides one
kept item is the failure this skill exists to prevent.
Anti-patterns → STOP
| Rationalization | Reality / Fix |
|---|---|
| "It's an old branch, nobody uses it" | Age proves nothing. Ancestor or same tree, or it stays. |
"Squash-merged, so -D is fine without checking" |
Check the tree first. A branch with one extra commit after the PR looks identical by name. |
| "The tree is dirty, I'll stash it to switch to main" | That is someone's work. Ask first; a stash is easy to forget for ever. |
"Pull failed, I'll reset --hard origin/main" |
That throws away local commits. Report the divergence instead. |
"I'll cd into each child to run git" |
git -C <repo>. A cd plants session state inside the child. |
| "I'll delete the remote branches as I go" | One list, one confirmation, at the end. |
| "Report says clean; the kept worktree is a detail" | Say what was kept and why. The report is the deliverable. |
Next: after a clean report there is nothing to hand over. Neighbours: ../worktrees/SKILL.md
opens isolation for new work; ../ship/SKILL.md lands one finished branch; npx @ericrisco/rsc upgrade updates the harness itself, which is a different "actualiza".
Orientación (siempre)
Habla con la voz de orient: frases cortas, una idea por frase, y cada respuesta se entiende sola. Registro técnico o con analogías según technical_level en 02-DOCS/wiki/harness/user-profile.md. Cierra cada turno con el bloque-brújula (📍 dónde estás · ➡️ siguiente, terminando en pregunta; ✅ y 🧭 cuando hay algo hecho o decidido). Nunca termines en seco. Protocolo completo: skill orient → skills/orient/references/orientation-contract.md. (Defiere a suggest el "¿instalo la skill que falta?".)