arbiterforge/codearbiter/ca-cleanup

Claude Code and agent skill by arbiterforge/codearbiter

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Clean up an already-merged local branch after proving containment. Confirm each discard and preserve unique work.

Install

npx vskill@latest install arbiterforge/codearbiter/ca-cleanup

SKILL.md

post-merge-cleanup

The walk back from a merged PR, selected by cleanup intent or an explicit entry.

The work has landed on the default branch. What is left is local: the branch you are standing on, and whatever the run left in the tree. This skill returns you to a clean, fast-forwarded default checkout without losing anything you have not explicitly agreed to lose.

It is ordinary lifecycle work and never needs `/ca-override`. When it cannot proceed, it names the gate that stopped it. Issue #308 recorded the alternative: with no owner for this transition, the routing loop reached for /ca-chore, then for /ca-override — a bypass manufactured to cover a coverage hole.

<!-- catalog-compatibility-notice:start -->

Compatibility route. Prefer /ca-pr --cleanup for new explicit usage. The retained entry
and natural-language intent use this owner under
<plugin-root>/includes/command-compatibility.md.

<!-- catalog-compatibility-notice:end -->

Entry boundaries

Select this owner for an already-merged branch cleanup request, /ca-pr --cleanup, or the explicit /ca-cleanup compatibility entry. Do not require repeated slash syntax. An explanation-only or dirty-state question is read-only, not a deletion request. An unmerged branch belongs to the finishing owner, not this cleanup. MUST NOT require /ca-override for ordinary cleanup; name a real blocker instead. This owner neither stages nor commits. Keeping work may hand it to the existing commit owner; no discard is implied by that handoff or by a cleanup request.

Pre-flight

Read this, or STOP and surface the gap — never guess the default-branch name:

  • <project-root>/.codearbiter/CONTEXT.md — the default-branch name.

If HEAD is already the default branch, there is no transition to make. Report the dirty state read-only and exit; branch pruning across other branches belongs to /ca-standup.

Phase 1 — Prove the merge · gate: BLOCK

A branch that looks merged is not merged. Establish it against the network, not against a stale local ref. This repo squash-merges by default, so SHA-ancestry alone is the wrong instrument for most landings: it holds for a fast-forward or a merge-commit landing, but a squash merge writes a new commit with no SHA lineage back to the branch, and --is-ancestor will report non-zero for a branch that landed cleanly. The gate is content-containment, proven by whichever instrument fits the landing, always reported as a fact:

  1. git fetch the remote holding the default branch. A fetch that fails STOPs —

an unfetched comparison proves nothing.

  1. Prove containment with two primary instruments plus a fallback, tried in

order:

  1. Ancestry — git merge-base --is-ancestor HEAD origin/<default>. Holds

for a fast-forward or merge-commit landing. If it holds, that is the proof: report it and move on.

  1. Squash-merge proof via the PR record — if ancestry fails, run

gh pr list --head "$(git branch --show-current)" --state merged --json number,state,headRefOid,mergeCommit. A MERGED PR whose headRefOid equals local git rev-parse HEAD proves every commit on this branch rode that PR's squash into the default branch. Report the PR number, that headRefOid == HEAD, the merge commit (mergeCommit.oid in that JSON), and the ancestry check's negative result — all as facts, not as a failure. Corroborate with git diff --quiet origin/<default> HEAD when it happens to hold; a non-empty diff alongside a valid PR proof is normal (the default branch advanced since the merge landed) and is reported as a fact, never treated as a failure. The PR-record identity — MERGED plus headRefOid == HEAD — is the load-bearing proof; the diff is corroboration only when it happens to be fresh, never a requirement.

  1. Fallback when `gh` is unavailable or no PR record exists —

git diff --quiet origin/<default> HEAD (the tree is byte-identical to the fetched default) is an acceptable fallback proof. Report it as such.

  1. Report whichever instrument held as a fact — the default branch, the fetched

SHA, and which of the three proofs established containment.

If none of the three hold, STOP exactly as today. Name the un-landed commits and route to /ca-pr; nothing is deleted here.

Deliberate deviation from issue #586's suggested contract: that suggestion
required the squash-merge proof's diff to be empty. That requirement
re-breaks this gate the moment any later PR merges to the default branch —
the common state, not an edge case. The PR-record identity is what proves
containment; the diff is corroboration, demoted from requirement to fact.

Gate: the remote is fetched and HEAD is proven contained in the fetched default branch — by ancestry, by the PR-record squash proof, or by the byte-identical fallback — or the skill has stopped.

Phase 2 — Classify the residue · gate: BLOCK

List every dirty tracked change, every untracked file, and every stash reachable from this branch. Classify each into exactly one of three, and say why for each:

  • Redundant — byte-identical to content already on the default branch, or a

regenerated build artifact whose generator is committed and rerunnable. Safe to remove because removing it loses no information.

  • Superseded — an earlier form of something the merged PR already landed in a

better shape. Name what supersedes it.

  • Unique — anything else. This is the default: an artifact that cannot be

proven redundant or superseded IS unique. Uncertainty classifies as unique, not as redundant.

Present the classification before touching anything. A file whose class you cannot establish is reported as unique with the reason you could not classify it.

Gate: every dirty, untracked, and stashed artifact carries a class and a stated reason, with unclassifiable items counted as unique.

Phase 3 — Resolve the residue · gate: STOP

Per item, in the order Phase 2 listed them. Never batch:

  • Unique — offer to keep it: /ca-commit it on this branch before the

transition, move it aside, or leave it in place and stop the cleanup. It is discarded only if the user explicitly confirms that item by name, with the Phase 2 reasoning in view.

  • Redundant / superseded — offer removal, one confirmation each, stating what

it is and why it is safe. Declining leaves it exactly where it is.

A stash is never dropped here. Stashes are reported with git stash show as the suggested next step, the same report-and-route contract /ca-standup holds.

If anything the user chose to keep would block the checkout, STOP and say so rather than removing it anyway — but distinguish the two causes before naming the artifact as the blocker:

  • The kept artifact genuinely conflicts — its content collides with what

checking out the default branch would need to change or remove. This is the user's to resolve, as today.

  • The local default ref lags the fetched one — git refuses a checkout when

a locally-modified tracked file differs between HEAD and the target ref, and a stale local <default> makes that difference larger than reality: content the kept artifact never actually touches can still collide with what a current default branch would carry. That gap is not the user's problem and not the kept artifact's fault. It is resolved by Phase 4's fast-forward-first step, not by discarding anything here. Issue #586 recorded the observed failure mode: a stale local main — 513 lines behind on .codearbiter/gate-events.log alone — made this STOP name a correctly-kept 38-line artifact as the blocker, which invites exactly the silent discard this phase exists to prevent.

Gate: every item is resolved by an explicit per-item decision, and the working tree is clean enough to check out the default branch — or the skill has stopped with the blocker named, correctly attributed to a genuine conflict or a stale local ref.

Phase 4 — Transition · gate: BLOCK

Only after Phase 3 leaves the tree safe:

  1. Fast-forward the local default ref BEFORE checking it out:

git fetch origin <default>:<default>. Git refuses a non-fast-forward update of a ref that is not currently checked out, so the --ff-only guarantee is preserved by the instrument itself — this cannot silently rewrite the local default ref, only advance it or refuse. This is what keeps a stale local default from blocking (or worse, misattributing) the checkout below. This step can itself be refused for the same reason a checkout can be — <default> checked out in another worktree — in which case report that as the (correctly attributed) blocker, per Phase 3, and stop; do not treat the refusal as license to skip ahead.

  1. Check out the default branch, then verify the checkout actually happened

(git branch --show-current). A checkout silently fails when another worktree holds the branch, and every step after this one would otherwise run against the wrong branch.

  1. Fast-forward with --ff-only (belt over the braces of step 1 — a no-op when

the pre-checkout fetch already brought the local ref current). A divergence means the default branch moved in a way this skill will not reconcile: report it and stop. Never a merge commit, never a rebase, never a reset.

Gate: the local default ref was fast-forwarded before the checkout — or the fast-forward's refusal was itself reported as a fact and the skill stopped — HEAD is confirmed on the default branch by re-read, and the belt-and-braces --ff-only pull either succeeded (typically a no-op) or was reported as a refused divergence.

Phase 5 — Delete the merged local branch · gate: STOP

Offer deletion of the now-merged local branch, with the Phase 1 containment proof restated. One confirmation, naming the branch.

  • git branch -d first, always. Never reach for -D on the assumption that a

refusal means -d can't handle a squash merge — it often can. git branch -d's safety check accepts a branch merged into its upstream, not only into HEAD: with an upstream still configured and equal to the tip, -d succeeds (with a warning) even for a squash merge that --is-ancestor alone would call unmerged. Do not conclude a refusal here means -D is required — check the reason first.

  • The sanctioned `-D` path. -d typically refuses once the remote branch

has been auto-deleted and pruned, because there is then no upstream left for -d to test reachability against. When that happens, git branch -D is permitted, but ONLY when both hold:

  1. Phase 1's proof, established this run, was the PR-record squash proof

(MERGED and headRefOid == HEAD) — not a bare ancestry pass, not a stale or assumed proof.

  1. The confirmation restates that Phase-1 proof and explicitly names the

branch. Frame this precisely: -d's safety check tests SHA-reachability, and the squash proof has already shown that instrument is the wrong one for this repo's merge mode. The PR-record proof plus a named confirmation replaces the check that -d can no longer run — it does not bypass it. Everywhere else, -D stays forbidden exactly as before: if -d refuses and Phase 1's proof was anything other than the PR-record squash proof, report both and stop.

  • The remote branch is never touched. If the user wants it gone, that is

theirs to do or the platform's auto-delete-on-merge to do.

Declining leaves the branch in place. That is a normal outcome, not a failure.

Gate: the branch is deleted only after an explicit confirmation naming it, via -d, or via -D restricted to the sanctioned path above with the proof restated, with the remote untouched.

Phase 6 — Receipt

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/arbiterforge/codearbiter/ca-cleanup/badge)](https://verified-skill.com/skills/arbiterforge/codearbiter/ca-cleanup)

Something wrong with this skill or its page? Tell us.