Refine feature and sprint intent into a useful specification. Capture scope, observable criteria and material choices without repeated artifact approval ceremonies.
中文简介正在准备中。当前显示原文说明。
Clean up an already-merged local branch after proving containment. Confirm each discard and preserve unique work.
中文简介正在准备中。当前显示原文说明。
查看安装方法安装前可以查看交给 AI 代理的指令内容。
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:end -->Compatibility route. Prefer
$ca-pr --cleanupfor new explicit usage. The retained entry and natural-language intent use this owner under includes/command-compatibility.md.
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.
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.
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:
git fetch the remote holding the default branch. A fetch that fails STOPs —
an unfetched comparison proves nothing.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.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.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.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.
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:
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.
Per item, in the order Phase 2 listed them. Never batch:
$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.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:
<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.
Only after Phase 3 leaves the tree safe:
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.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.--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.
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.-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:
MERGED and headRefOid == HEAD) — not a bare ancestry pass, not a
stale or assumed proof.-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.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.
One short summary: what landed, what was removed, what was kept, and where HEAD is now. State declines as declines — a cleanup the user stopped halfway is a correct outcome reported plainly, not an error.
HEAD is contained in the fetched default branch
before any deletion — via ancestry, via the PR-record squash proof (MERGED
and headRefOid == HEAD), or, without gh, via a byte-identical tree diff.
MUST NOT infer merge state from a : gone] upstream alone.git fetch origin <default>:<default>)
before checking it out, or report the fast-forward's own refusal (e.g.
<default> checked out in another worktree) as the blocker and stop. MUST
re-read the current branch after checkout before acting on it.--ff-only, and MUST NOT merge, rebase, or reset to reach the default
branch.git branch -d, and MAY use -D ONLY when the Phase-1 squash-merge
proof held this run and -d refused, with that proof restated and the branch
named in the confirmation. Everywhere else -D is forbidden. MUST NOT delete a
remote branch, force-push, or write to the default branch.$ca-standup does.$ca-override when blocked. Name the gate instead.还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
Refine feature and sprint intent into a useful specification. Capture scope, observable criteria and material choices without repeated artifact approval ceremonies.
中文简介正在准备中。当前显示原文说明。
Vet a new or changed third-party dependency for license, provenance, and supply-chain risk before any install runs.
中文简介正在准备中。当前显示原文说明。
Record user-decided ADRs or inspect their health read-only. Preserve attribution and acceptance evidence.
中文简介正在准备中。当前显示原文说明。
Inspect ADR health read-only; optionally select one ADR with --adr N.
中文简介正在准备中。当前显示原文说明。
Assemble the governance record for a range — commits, overrides, ADRs, sprint auto-decisions, open questions, checkpoint findings — into one dated audit packet. Read-only.
中文简介正在准备中。当前显示原文说明。
Lightweight Q&A about the project — answer from context and return, no routing, no state change.
中文简介正在准备中。当前显示原文说明。