Skip to content
Closed
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.2",
"version": "0.13.4",
"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
13 changes: 13 additions & 0 deletions plugins/repo-fleet-hygiene/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,19 @@
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.4]

### Fixed

- **`reclaimable-worktree` no longer treats ignored files as invisible (#2601).** Classification now
uses `git status --porcelain --ignored --untracked-files=normal`. Every reclaimable finding names
ignored entries or states there are none; regenerable-only ignored paths (`node_modules/`,
`target/`, `.venv/`, `bin/`, `obj/`, `dist/`) keep the reclaimable disposition. Non-regenerable
ignored content emits `worktree-ignored-content` instead of a safe-to-delete reclaimable handoff.
Stash wording now states that `refs/stash` is repository-global and unaffected by worktree
removal; stash state is collected once per repository. Local `status.showUntrackedFiles=no`
cannot hide untracked or ignored evidence from this probe.

## [0.13.2]

### Fixed
Expand Down
9 changes: 6 additions & 3 deletions plugins/repo-fleet-hygiene/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,12 @@ successfully audited.
"Can I delete this repository safely?" is deletion triage — an inventory of dirty files, stashes,
and unpushed branches — and belongs to `/repo-hygiene:clean` (its scan/stash/git tiers), which owns
per-repository disposability analysis. This audit is a read-only cross-repository evidence REPORT;
it names candidates and hands off. It can report `reclaimable-worktree` when a linked worktree's
`git status --porcelain` is empty, but that is working-tree evidence only — not proof a checkout is
still wanted, and not repository deletion triage.
it names candidates and hands off. It can report `reclaimable-worktree` when a linked worktree has
no tracked/untracked changes under `git status --porcelain --ignored` and either no ignored entries
or only regenerable ones, but that is working-tree evidence only — not proof a checkout is still
wanted, and not repository deletion triage. Non-regenerable ignored content is reported as
`worktree-ignored-content` rather than offered as safe-to-delete. Stashes are repository-global and
unaffected by worktree removal.

## Requirements

Expand Down
16 changes: 11 additions & 5 deletions plugins/repo-fleet-hygiene/skills/audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,15 @@ The bundled collector is authoritative for classifications. Preserve its evidenc
never worktree evidence. Compare each existing registered path's actual `--git-common-dir` with
the canonical checkout's expected common dir. A mismatch is `HIGH` evidence of an administrative
linkage problem but **manual review only**. Missing/prunable registrations never trigger pruning.
Linked registrations with reliable admin and an empty `git status --porcelain` at the work-tree
root emit `MEDIUM` `reclaimable-worktree` — working-tree cleanliness only, not proof the
checkout is still wanted; stash list is out of scope. A failed status probe emits `UNKNOWN`
`worktree-disposability-unverifiable`. If either inventory command fails or emits malformed/partial output, discard it, emit `UNKNOWN`,
Linked registrations with reliable admin and no tracked/untracked changes under
`git status --porcelain --ignored` emit `MEDIUM` `reclaimable-worktree` when ignored entries are
absent or regenerable only (`node_modules/`, `target/`, `.venv/`, `bin/`, `obj/`, `dist/`) —
working-tree cleanliness only, not proof the checkout is still wanted; every such finding names
its ignored entries or states there are none. Non-regenerable ignored content emits `MEDIUM`
`worktree-ignored-content` instead, naming what directory removal would destroy. Stashes are
repository-global (`refs/stash` in the common gitdir), so they are unaffected by worktree
removal; the collector records stash state once per repository and never per worktree. A failed
status probe emits `UNKNOWN` `worktree-disposability-unverifiable`. If either inventory command fails or emits malformed/partial output, discard it, emit `UNKNOWN`,
stop local branch/worktree classification, and do not count that repository as successfully
audited; an empty/failed inventory never means no branches are attached.
5. **Protection:** current/default/worktree-attached branches are never emitted as standalone branch
Expand Down Expand Up @@ -179,10 +184,11 @@ do not turn "no verified finding" into "fleet is clean".
|---|---|
| `merged-local-branch` | Run `/repo-hygiene:clean git` in the named canonical repository |
| `merged-worktree`, `prunable-worktree`, `missing-worktree`, `reclaimable-worktree` | Run `/source-control:worktree cleanup --dry-run` in the canonical repository |
| `worktree-ignored-content` | Inspect the named ignored paths before any worktree cleanup; cleanliness alone is not safe-to-delete |
| `worktree-admin-mismatch` | Manual inspection; `git worktree repair` is an option only after validating which administrative directory is authoritative |
| `worktree-not-a-root` | Manual inspection of the registered path; a `git -C` probe of it describes the CONTAINING repository, so no cleanup handoff is safe until the path is resolved |
| `worktree-root-unverifiable` | Manual inspection of the registered path. Root-ness is unproven here rather than disproven — the probe itself failed — so infer nothing about the path in either direction |
| `worktree-disposability-unverifiable` | Manual inspection of the registered path; reclaimability was not checked because `git status --porcelain` failed |
| `worktree-disposability-unverifiable` | Manual inspection of the registered path; reclaimability was not checked because `git status --porcelain --ignored` failed |
| `worktree-nested-in-repository` | Recreate at an external root with `/source-control:worktree create`, then remove the nested one |
| `worktree-placement-unverifiable` | Inspect the canonical checkout; placement was not checked for any of its worktrees, so their placement is unknown rather than confirmed |
| `github-remote-moved` | Human-reviewed `git remote set-url`; this plugin never changes remotes |
Expand Down
18 changes: 15 additions & 3 deletions plugins/repo-fleet-hygiene/skills/audit/evals/evals.json
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,27 @@
{
"id": 13,
"name": "reclaimable-worktree-is-working-tree-evidence-not-wantedness",
"prompt": "The audit reports a MEDIUM reclaimable-worktree finding for a linked worktree whose git status --porcelain is empty. Does that mean nobody is using it and I can delete it?",
"expected_output": "No. reclaimable-worktree means only that the working tree has no uncommitted changes at audit time; it is not evidence that the checkout is still wanted. The handoff is a dry-run through /source-control:worktree cleanup, and the human decides.",
"prompt": "The audit reports a MEDIUM reclaimable-worktree finding for a linked worktree whose git status --porcelain --ignored shows no tracked/untracked changes and no non-regenerable ignored content. Does that mean nobody is using it and I can delete it?",
"expected_output": "No. reclaimable-worktree means only that the working tree has no uncommitted tracked/untracked changes and no non-regenerable ignored content at audit time; it is not evidence that the checkout is still wanted. Stashes are repository-global and unaffected by worktree removal. The handoff is a dry-run through /source-control:worktree cleanup, and the human decides.",
"files": [],
"expectations": [
"Declines to equate an empty porcelain status with nobody still needing the checkout",
"Declines to equate porcelain cleanliness with nobody still needing the checkout",
"Names reclaimable-worktree as working-tree evidence only",
"Does not infer disuse from directory naming, age, or activity",
"Routes the decision to the worktree cleanup dry-run and the human"
]
},
{
"id": 14,
"name": "non-regenerable-ignored-content-is-not-silent-reclaimable",
"prompt": "A linked worktree has an empty ordinary git status --porcelain, but git status --porcelain --ignored shows !! .work/handoffs/. The audit emitted worktree-ignored-content instead of reclaimable-worktree. Why, and can I delete the worktree directory?",
"expected_output": "Ordinary porcelain cannot see ignored files, and removing the worktree directory would destroy them. worktree-ignored-content names the non-regenerable ignored paths and is manual review, not a safe-to-delete reclaimable handoff. Inspect those paths before any cleanup.",
"files": [],
"expectations": [
"Explains that plain porcelain misses ignored content that directory removal destroys",
"Treats worktree-ignored-content as a warning, not a reclaimable/safe-to-delete signal",
"Requires inspecting the named ignored paths before cleanup"
]
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ failure rather than a discovery.
| `worktree-not-a-root` | Registered path exists but `git rev-parse --show-prefix` is non-empty, so it is a subdirectory of a work tree rather than its root — `git -C` answers for the CONTAINING repository at exit 0, which is indistinguishable from a healthy clean worktree | `HIGH` | Manual review; never read a `git -C` probe of the path as this worktree's own state |
| `worktree-root-unverifiable` | `git rev-parse --show-prefix` failed at the registered path, so root-ness is unproven | `UNKNOWN` | Stop worktree classification for that registration; do not infer either way |
| `worktree-nested-in-repository` | A non-main registration's root is inside the canonical checkout's own working tree, rather than at an external root outside every repository | `MEDIUM` | Manual placement decision; never auto-move or auto-remove |
| `reclaimable-worktree` | A linked, unlocked registration with reliable admin has an empty `git status --porcelain` at its work-tree root | `MEDIUM` | Candidate worktree dry-run handoff; emptiness is working-tree evidence only, not proof nobody still needs the checkout |
| `worktree-disposability-unverifiable` | `git status --porcelain` failed at a linked registration whose admin and root-ness were otherwise reliable | `UNKNOWN` | Do not infer whether the worktree is reclaimable |
| `reclaimable-worktree` | A linked, unlocked registration with reliable admin has no tracked/untracked `git status --porcelain --ignored` changes at its work-tree root, and either no ignored entries or only regenerable ones (`node_modules/`, `target/`, `.venv/`, `bin/`, `obj/`, `dist/`). Evidence names the ignored entries or states there are none | `MEDIUM` | Candidate worktree dry-run handoff; cleanliness is working-tree evidence only, not proof nobody still needs the checkout. Stashes are repository-global (`refs/stash` in the common gitdir) and are unaffected by worktree removal |
| `worktree-ignored-content` | A linked, unlocked registration with reliable admin has no tracked/untracked porcelain changes, but non-regenerable ignored content is present and would be destroyed by removing the worktree directory | `MEDIUM` | Manual review; do not treat cleanliness alone as safe-to-delete — the finding names the destroyable ignored paths |
| `worktree-disposability-unverifiable` | `git status --porcelain --ignored` failed at a linked registration whose admin and root-ness were otherwise reliable | `UNKNOWN` | Do not infer whether the worktree is reclaimable |
| `worktree-placement-unverifiable` | A non-bare canonical checkout gave no working-tree root, so no registration under it could be placement-checked. A BARE hub is not this finding — it has no working tree for a worktree to be nested inside, so the check is legitimately skipped rather than unanswered | `UNKNOWN` | Do not infer that this repository's worktrees are correctly placed |
| `github-remote-moved` | GitHub REST resolves the requested `owner/repo` to a different canonical `full_name`. Branch and worktree analysis continues against the resolved identity; this finding does not stop local classification | `HIGH` | Human-reviewed remote update; local classification is not deferred |
| `duplicate-checkout` | Two or more distinct checkouts resolve to one normalized GitHub identity | `LOW` | Informational only; same-identity clones legitimately diverge |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,12 @@ is explicit authenticated GitHub metadata lookup initiated by the user-invoked a
- Repository/config/worktree-derived report values containing newlines or control/ANSI bytes are
rendered as a single `%q`-encoded field, so they cannot forge report labels or terminal controls.
- A worktree-looking directory cannot become a finding without Git porcelain membership.
- `git status --porcelain` at a registered work-tree root is read-only local metadata; it never
transmits content and cannot mutate. A failed status probe cannot be mistaken for a clean tree.
- `git status --porcelain --ignored` at a registered work-tree root is read-only local metadata; it
never transmits content and cannot mutate. A failed status probe cannot be mistaken for a clean
tree. Ignored entries are classified so regenerable build artifacts do not block reclaimability
while non-regenerable ignored content cannot be destroyed without being named.
- `git stash list` is read once per repository (never per worktree): `refs/stash` is shared in the
common gitdir, so per-worktree collection would imply an isolation that does not exist.
- A high-confidence finding still cannot mutate because the script has no apply mode.

## Deferred verification
Expand Down
Loading