agentsclimarketplace

Squash merge detection union

Skill kjuhwa/skills-hub/skills/workflow/squash-merge-detection-union

Classify "can this branch be cleaned up?" using the union of `git branch --merged` + `git cherry` patch-equivalence + `gh pr list` state, with clear semantics for each signal.From its SKILL.md

Install
npx -y skills add kjuhwa/skills-hub --skill squash-merge-detection-union

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

5.2 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it

Three-Way Merged-Branch Detection (--merged ∪ cherry ∪ PR state)

When to use

  • You clean up stale git worktrees / branches and want to distinguish "safe to delete" from "unmerged, don't touch."
  • Squash merges break git branch --merged main because the squashed commit on main has a different SHA than any of the feature commits. Ancestry-based checks say "unmerged" even when the work is long shipped.
  • You have gh CLI available for GitHub repos and want to use it as a tie-breaker when git alone is ambiguous.

Steps

Run checks in this order, returning early on a definitive signal:

  1. Fast path — git branch --merged <main>. If the branch appears in the output (minus any leading * ), it was fast-forward or merge-commit merged. Return "safe, no open PR."

    const { stdout } = await exec('git', ['branch', '--merged', mainBranch]);
    const merged = stdout.split('\n').map(b => b.trim().replace(/^\* /, ''));
    if (merged.includes(branchName)) return { safe: true, openPr: false };
    
  2. Squash-merge detection — git cherry <base> <branch>. Lines starting with - mean "patch already in upstream" (squash-merged or cherry-picked). Lines starting with + mean "genuinely not in upstream." If every line is - (or the list is empty), the branch is patch-equivalent to upstream — safe to delete.

    const lines = stdout.split('\n').filter(l => l.trim());
    if (lines.length === 0) return true;
    return lines.every(line => line.startsWith('-'));
    
  3. PR state — gh pr list --head <branch> --state all --json state --limit 1. Parse the JSON, map MERGED → safe, CLOSED → safe-if-includeClosed-flag, OPEN → unsafe (and flag openPr: true), anything else → unknown (treat as unsafe).

  4. Error handling per signal:

    • For branch --merged / git cherry: expected errors (unknown revision, bad revision, not a git repo, ENOENT) → return false/null, let the next signal try. Unexpected errors (permission denied, corruption) → throw.
    • For gh: missing binary (ENOENT or "command not found") → return 'NONE' silently (gh is a soft dependency). Other errors → log at warn with stdout preview.
    • For the remote-URL precheck: if origin isn't github.com, return 'NONE' and skip gh. Non-GitHub remotes are out of scope.
  5. Cache PR-state lookups per cleanup invocation. getPrState takes an optional Map<string, PrState> so repeated queries across many branches don't re-exec gh (it's slow, ~100-300 ms per call).

  6. Combine signals with OR logic in a wrapper (computeBranchCleanupSafety or similar): the branch is safe iff any of the three signals says so. The return type should include { safe: boolean, openPr: boolean } so callers can message the user differently for "has open PR" vs "just stale."

Counter / Caveats

  • Do not run git cherry without the --merged fast path — it's O(commits-on-branch) whereas --merged is O(heads). Order matters.
  • git cherry on an unpushed branch vs. a branch that has been rebased onto main can give surprising output (the rebased commits are patch-equivalent only to specific upstream commits). Test with a rebase scenario.
  • gh asks the GitHub API, which is rate-limited. The per-invocation cache is mandatory for bulk cleanup. Don't switch to unauthenticated API even as a fallback.
  • gh returns state: 'MERGED' even if the merge commit was force-pushed (a maintainer amend). Treating it as safe is correct because the platform considers it merged.
  • Non-GitHub remotes (Gitea, GitLab, custom) will fall through to the ancestry checks only. That's fine — just document it.

Evidence

  • packages/git/src/branch.ts:
    • isBranchMerged (lines 186-221) — git branch --merged.
    • isPatchEquivalent (lines 236-271) — git cherry with lines starting with - meaning already-upstream.
  • packages/isolation/src/pr-state.ts (91 lines): gh pr list --json state wrapper with per-invocation cache, GitHub-only remote guard, graceful handling of missing gh binary.
  • packages/core/src/services/cleanup-service.ts:562-578: the three-signal union inside computeBranchCleanupSafety / classifyForCleanup:
    if (await isBranchMerged(...)) return { safe: true, openPr: false };
    if (await isPatchEquivalent(...)) return { safe: true, openPr: false };
    const prState = await getPrState(...);
    if (prState === 'MERGED') return { safe: true, openPr: false };
    if (prState === 'CLOSED') return { safe: includeClosed, openPr: false };
    if (prState === 'OPEN') return { safe: false, openPr: true };
    
  • Commit SHA: d89bc767d291f52687beea91c9fcf155459be0d9.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 625,150. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.