Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/repo-fleet-hygiene/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "repo-fleet-hygiene",
"version": "0.13.0",
"version": "0.13.1",
"description": "Read-only Git/GitHub fleet audit for merged local branches, orphaned or mismatched worktree registrations, and repository transfers or renames. Findings are confidence-tiered and hand off exact targets to existing per-repository cleanup tools; this plugin never deletes branches or worktrees.",
"author": {
"name": "Melodic Software",
Expand Down
10 changes: 10 additions & 0 deletions plugins/repo-fleet-hygiene/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@
All notable changes to `repo-fleet-hygiene` are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.13.1]

### Fixed

- **`merged-pr-tip-drift` evidence no longer claims commits "may never have been pushed" (#2603).**
Absence from the last-fetched remote-tracking ref does not prove the tip was never on GitHub —
post-merge head deletion plus prune is the common case, and the tip object may still exist on the
remote. The non-matching push-state clause now states that the tip differs from the merged PR
`headRefOid` and that commits may still be on the remote.

## [0.13.0]

### Added
Expand Down
10 changes: 6 additions & 4 deletions plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1382,15 +1382,17 @@ analyze_repo() {
fi
elif [[ -n "$pr_any" && "$branch" != "$default_branch" ]]; then
IFS='|' read -r pr_num pr_oid pr_merged pr_url <<<"$pr_any"
# Whether the drift commits were ever pushed changes the risk profile of cleanup, so the
# evidence names the push state from the already-collected remote-tracking inventory --
# purely local, no network. A remote-tracking ref only records what the remote advertised
# Whether the local tip matches the last-fetched remote-tracking ref changes the cleanup
# risk profile, so the evidence names that observation from the already-collected inventory
# -- purely local, no network. A remote-tracking ref only records what the remote advertised
# at the LAST FETCH (the branch may have been deleted or force-pushed since), so the
# evidence is framed as cached local observation, never as current remote reachability.
# Absence from that ref does not prove the tip was never pushed: post-merge head deletion
# plus prune is common, and the tip object may still exist on the remote under another ref.
if [[ "$remote_inventory_failed" == "true" || -z "$canonical_remote" ]]; then
push_state="remote-tracking inventory unavailable, push state unknown"
else
push_state="local tip not on the last-fetched remote-tracking ref (drift commits may never have been pushed)"
push_state="local tip not on the last-fetched remote-tracking ref (tip differs from merged PR headRefOid; commits may still be on the remote)"
for ((ri = 0; ri < ${#REMOTE_BRANCH_NAMES[@]}; ri++)); do
if [[ "${REMOTE_BRANCH_NAMES[$ri]}" == "$branch" && "${REMOTE_BRANCH_TIPS[$ri]}" == "$tip" ]]; then
push_state="local tip matches the last-fetched remote-tracking ref (pushed as of the last fetch; verify current remote state before relying on recoverability)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,8 @@ for-each-ref)
case "$base" in
canonical-a)
# stale/gone: merged-PR batch row exists at a different OID (drift) but the branch has no
# remote-tracking ref -- the drift finding must state the local tip is absent (unpushed).
# remote-tracking ref -- the drift finding must state tip/headRefOid differ without claiming
# the commits were never pushed (they may still be on the remote).
printf 'main\tmain-a\0\nfeature/shared\tsha-a\0\nstale/changed\tdrift-tip\0\nfeature/mismatch\tmismatch\0\nstale/gone\tgone-tip\0\n'
;;
repo-b)
Expand Down Expand Up @@ -583,11 +584,14 @@ assert_contains "privacy-gated handoff distinguishes never-pushed locals" \
"Never-pushed locals: push to publish the branch name, then rerun"

# Drift push-state evidence: stale/changed has a same-named remote-tracking ref at the SAME OID
# (pushed); stale/gone has a drift-batch row but NO remote-tracking ref (may be unpushed).
# (pushed as of last fetch); stale/gone has a drift-batch row but NO remote-tracking ref — that
# absence must not be framed as "never pushed" (#2603).
assert_contains "pushed drift named in evidence" \
"current local tip is drift-tip; local tip matches the last-fetched remote-tracking ref (pushed as of the last fetch; verify current remote state before relying on recoverability)"
assert_contains "unpushed drift named in evidence" \
"current local tip is gone-tip; local tip not on the last-fetched remote-tracking ref (drift commits may never have been pushed)"
assert_contains "absent remote-tracking tip named without unpushed claim" \
"current local tip is gone-tip; local tip not on the last-fetched remote-tracking ref (tip differs from merged PR headRefOid; commits may still be on the remote)"
assert_not_contains "tip-drift evidence never claims unpushed without proof" \
"may never have been pushed"

# Header names the authenticated gh account; a failed login probe degrades to the plain line.
assert_contains "header names gh account" "GitHub evidence: available (account: test-login)"
Expand Down