agentsclimarketplace

Campaign status

Skill Oppenheimerdinger/ohd/skills/campaign-status

This skill should be used when asked "is X merged?" / "머지됐어?" / "머지 확인해줘", before claiming any branch is merged or unmerged, before pin/clean/re-run, or whenever a note asserts merge status — derives the verdict from git refs + the PR API, never from memory, and catches the squash-merge trap that fabricates phantom backlogs.From its SKILL.md

Install
npx -y skills add Oppenheimerdinger/ohd --skill campaign-status

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

  • 2 stars2 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

3.6 KB, as published. Nobody here has run it

Campaign status — merge-status is derived, never remembered

A note that says "pushed, awaiting merge" goes stale the moment someone merges. Trusting it afterwards fabricates a phantom land backlog. Merge status is a property of the git graph + the PR API, not of any note — re-derive it every time. Never answer "is X merged?" from memory, a campaign doc, or a memory file.

The squash-merge trap

GitHub's default merge is a squash: it creates a NEW commit on trunk, so the campaign branch is NOT an ancestor of trunk, and git merge-base --is-ancestor returns false for a branch that is genuinely, fully merged. Ancestry alone → false UNMERGED → phantom backlog.

Verdict rule: MERGED = ancestry MERGED or a MERGED PR exists. UNMERGED only when BOTH signals are negative and the branch still exists.

Reconciliation (parameterize BR / TRUNK)

BR=<branch> TRUNK=main
git fetch origin -q --prune                                   # prune stale refs

# 0) branch existence FIRST (avoid fatal → false UNMERGED)
git rev-parse -q --verify "origin/$BR" >/dev/null || echo "NO-BRANCH"

# 1) ancestry signal (catches ff/rebase merges; false for squash)
git merge-base --is-ancestor "origin/$BR" "origin/$TRUNK" && echo "MERGED (ancestry)"

# 2) PR-API signal (catches squash; baseRefName exposes the stacked trap)
gh pr list --head "$BR" --state all --json number,state,mergedAt,baseRefName

# 3) content-reach proof — only when signals disagree, or a MERGED PR has base≠trunk:
#    pick a token that exists ONLY in this campaign's diff, then
git show "origin/$TRUNK:<file>" | grep "<token>"

If step 0 prints NO-BRANCH, stop — don't run steps 1–3.

(campaign.sh status <name> runs 0–2 for you and prints the verdict.)

Verdict table

SignalsVerdict
ancestry trueMERGED
ancestry false, MERGED PR with base=trunkMERGED (squash — confirm with step 3 if anything looks off)
ancestry false, MERGED PR with base≠trunkSTACKED — content is on the base, maybe not trunk; step 3 is mandatory (the script prints STACKED?)
ancestry false, PRs only OPEN / CLOSED with mergedAt=null / noneUNMERGED — genuine, safe to land
ancestry false, gh unavailable OR the PR API call failsUNVERIFIED — a squash merge cannot be ruled out; check the PR page; NEVER treat as UNMERGED
origin branch absentNO-BRANCH (cleaned post-merge, or never pushed)

Rules: multiple PRs per head → MERGED if ANY is merged. A CLOSED PR with mergedAt: null was closed WITHOUT merging — not merged. The step-3 token must exist only in this campaign's diff, or the grep can false-positive.

Scope of a MERGED verdict — say this footer with it: MERGED is a git/PR FACT only. It is not a claim the change is enabled or reachable on the default path (that is campaign-land Phase 2.5), nor that the work is correct (that is the validation gate). Relaying a bare "MERGED" as "done/live" is the over-read this footer exists to stop.

When to run

Before claiming a land backlog / "awaiting merge" / "needs landing"; before pin (the dep branch must really be in the dep trunk) and before clean (never delete an unmerged branch); before re-running or rebasing a campaign; whenever any note asserts a merge status.

Keep looking

Skills are one crate of 627,801. 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.