Skip to content

feat(skills): add branch-supersede-check - #183

Merged
MCamner merged 4 commits into
mainfrom
feat/branch-supersede-skill
Aug 16, 2026
Merged

feat(skills): add branch-supersede-check#183
MCamner merged 4 commits into
mainfrom
feat/branch-supersede-skill

Conversation

@MCamner

@MCamner MCamner commented Aug 10, 2026

Copy link
Copy Markdown
Owner

git diff main...branch is the wrong instrument for the question "does this branch still matter", and it fails silently.

The three-dot form compares against the merge base — the point where the branch forked. It lists everything the branch did since then, whether or not trunk has since acquired the same content by another route: a squash merge, a cherry-pick, someone doing the work again in a different PR. A branch whose work landed months ago produces exactly the same shape of output as one holding something unique. Same file count, same additions, same confident +325 insertions.

Four branches in one day looked unmerged and were not. What settled each was a per-file comparison against trunk as it is now.

What the script does

Labels every file the branch touched:

IDENTICAL       docs/ARCHITECTURE.md
BASE-AHEAD      docs/POSTING_FLOW.md  (main has 6 line(s) the branch lacks)
DIVERGED        .github/workflows/static-site-checks.yml  (+4/-9)
ONLY-ON-BRANCH  skills/command-template-library/SKILL.md

identical 7 · base-ahead 2 · branch-ahead 0 · diverged 2 · only-on-branch 0  (of 11)

It prints the three-dot file count alongside, so the two measurements can be compared in one output.

Building it changed what it says

The first version gave a binary verdict. Run against all four real branches, it answered "has unique content" every time — technically true, and misleading every time, since all four were right to delete.

The classification was correct; the verdict was overconfident. So it now:

  • looks up a merged PR for the branch head — squash merging leaves neither ancestry nor identical files, so a squash-merged branch can look entirely unique. Verified: merged PR for this head: #176 merged 2026-08-08VERDICT: superseded.
  • grades rather than rulesreview, leaning superseded — 9 of 11 file(s) are identical to main or behind it.
  • states its limit — every non-superseded verdict ends with "This tool compares text, not worth. A DIVERGED file often means the branch is simply older."

SKILL.md carries three real cases where "unique content" was true and deleting was still right, so the next reader calibrates against reality rather than the label.

Placement

skills/ here rather than staying loose in ~/.agents/skills, so it gets version history, shell lint, and the discoverability check added in #182. .claude/skills/branch-supersede-check symlinks to it, and ~/.agents/skills/branch-supersede-check now points at this copy too — one source of truth reachable by both agent tools.

Read-only: never checks out, merges, resets or deletes. The single network call is the PR lookup, skippable with --no-pr.

Verification

shellcheck -S warning — clean
./scripts/check-skills.sh — OK
mq-skills audit — macos-scripts: 8 skill(s), 8 indexed, 8 discoverable
[PASS] Shell lint passed at warning severity (198 files)
[PASS] All selftest checks passed.

Behaviour reproduced against the two real branches it was built from: a squash-merged one classified superseded, and a 7-of-11-identical one classified review, leaning superseded. Error paths checked too — unknown ref exits 2, no argument prints usage.

🤖 Generated with Claude Code

MCamner and others added 4 commits August 10, 2026 02:05
`git diff main...branch` is the wrong instrument for "does this branch still
matter", and it fails silently. The three-dot form compares against the merge
base, so it lists everything the branch did since it forked — whether or not
trunk has since acquired the same content by a squash merge, a cherry-pick, or
someone doing the work again in another PR. A branch whose work landed months
ago renders exactly like one holding something unique.

Four branches in one day looked unmerged and were not. What settled each of
them was a per-file comparison against trunk as it is now, which is what this
script automates: every touched file is labelled IDENTICAL, BASE-AHEAD,
BRANCH-AHEAD, DIVERGED or ONLY-ON-BRANCH.

Building it changed what it says. The first version gave a binary verdict, and
against all four real branches it answered "has unique content" — technically
true every time, and misleading every time. So it now looks up a merged PR for
the branch head (squash merging leaves neither ancestry nor identical files),
grades the verdict rather than ruling, and ends with the limit stated outright:
this tool compares text, not worth. A DIVERGED file usually means the branch is
behind, not ahead.

Read-only. Never checks out, merges, or deletes; the single network call is the
PR lookup, skippable with --no-pr.

Symlinked into .claude/skills so it is discoverable, and ~/.agents/skills points
at this copy too — one source of truth for both agent tools.

Verified: shellcheck clean, check-skills OK, mq-skills audit reports 8 skills
8 indexed 8 discoverable, full selftest suite green, and the script reproduces
the two real classifications it was built from (a squash-merged branch as
superseded, and a 7-of-11-identical branch as leaning superseded).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A branch sharing no history with trunk was reported as superseded. Without a
merge base the three-dot diff fails, the failure was discarded, and the empty
file list was counted as zero unique files — which reads exactly like a branch
whose work already landed. The tool exists to prevent wrong deletions and was
recommending one.

Both halves are now guarded: a missing merge base is a lookup error, and the
diff is taken once with its status checked rather than run twice into /dev/null.

The merged-PR lookup asked GitHub for 200 recent PRs and filtered locally, so
an older branch — the usual subject of this question — could fall off the end.
It now filters server-side by head branch, and a failed lookup says so instead
of passing for "no merged PR exists".

Adds tests/branch-supersede-smoke.sh; the script had no test at all.
The script had no row in tests/manifest.tsv and no line in test-all.sh, so the
new test would have sat there unrun — the exact failure that manifest exists to
catch. SKILL.md documented the old 200-PR lookup and described exit 2 as bad
usage only.
@MCamner

MCamner commented Aug 15, 2026

Copy link
Copy Markdown
Owner Author

P1 fixed — and it was worse than the report

Reproduced first. An orphan branch holding a file main has never seen:

=== lonely (91e463c) vs main (ce7573b) ===
  three-dot diff (main...lonely) touches 0 file(s)
  identical 0 · base-ahead 0 · branch-ahead 0 · diverged 0 · only-on-branch 0  (of 0)

VERDICT: superseded — nothing here is missing from main.
EXIT=0

So the tool recommended deleting a branch that shares not one commit with trunk
and carries work trunk does not have. Exactly the class this repo spent the
Pulse series eliminating: comparison failed + empty output ≠ superseded.

Two guards, both added:

  • a missing merge base is a lookup error (exit 2), before any report is printed
  • the three-dot diff is taken once with its status checked, instead of run
    twice into /dev/null — the count and the loop could not disagree, and any
    other diff failure stops the run rather than emptying it

They are redundant by design: either alone closes the hole. The test proves the
hole is closed, not which line closes it — removing both restores the bug and
step 1 fails.

P2 fixed

gh pr list --state merged --head "$name", filtered by GitHub. gh 2.97.0
supports -H/--head, verified. This also removes the jq expression built by
interpolating a branch name.

One thing the report did not raise, found while testing it: a failed lookup
was indistinguishable from no merged PR exists. That matters more here than
elsewhere — the merged-PR lookup is the evidence a squash merge destroys, so
its absence is what pushes a verdict toward "unique". A failed lookup now says
so and the verdict does not silently lean on it.

Test

tests/branch-supersede-smoke.sh — the script had none, and no row in
tests/manifest.tsv, so it was entirely ungated. Six steps: unrelated
histories, content landed by another route, genuinely unique content, unknown
ref, the lookup shape, the failed lookup.

Verified failable against three planted defects (guards removed, --limit 200
restored, gh failure swallowed) — each caught, tree restored.

Full suite: 85 active tests, shell lint at warning severity over 216 files,
markdownlint clean. Ran the tool for real against
backup/local-main-stray-commitONLY-ON-BRANCH hub, verdict review, exit 1.

main merged in; the branch was three PRs behind. Ready for the second review.

@MCamner
MCamner merged commit 97c149d into main Aug 16, 2026
3 checks passed
@MCamner
MCamner deleted the feat/branch-supersede-skill branch August 16, 2026 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant