Coordinate Git/GitHub hygiene across a cross-repository fleet: discover canonical repositories, collect and roll up cross-repository evidence (including merged remote-tracking heads still on origin), and hand an action plan to repo-hygiene/source-control, which own per-repository cleanup. The current collector is read-only and emits detailed exact handoffs; it never deletes, prunes, repairs, fetches, checks out, or rewrites. Use when: 'audit repositories across a fleet', 'stale branches acros...
Installs into .claude/skills of the current project.
Are you the author of Audit?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/melodic-software-audit-4881b1ce)
---
description: "Coordinate Git/GitHub hygiene across a cross-repository fleet: discover canonical repositories, collect and roll up cross-repository evidence (including merged remote-tracking heads still on origin), and hand an action plan to repo-hygiene/source-control, which own per-repository cleanup. The current collector is read-only and emits detailed exact handoffs; it never deletes, prunes, repairs, fetches, checks out, or rewrites. Use when: 'audit repositories across a fleet', 'stale branches across repos', 'orphaned worktrees across repos', 'merged remote branches still on origin', 'moved or renamed GitHub repos'."
user-invocable: true
disable-model-invocation: false
argument-hint: "[[<dir>]... [--root <dir>]... [--repo <dir>]... [--config <file>] [--detail]|--apply-plan <path>]"
allowed-tools:
- Bash(${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh:*)
metadata:
workflow-stage: operator
summary: Discover a repository fleet and coordinate read-only evidence handoffs
cadence: weekly
---
**Arguments.** `[[<dir>]... [--root <dir>]... [--repo <dir>]... [--config <file>] [--detail]|--apply-plan <path>]`. Full form: [[<dir>]... [--root <dir>]... [--repo <dir>]... [--config <file>] [--canonical <github.com/owner/repo=path>]... [--skip <name>]... [--extend-skip <name>]... [--max-depth <1..12>] [--detail] [--plan-file <path>]|--apply-plan <path>]
## Purpose
Coordinate cross-repository hygiene. This skill owns bounded fleet discovery, canonical
checkout resolution, fleet-scale evidence collection, rollup, and action-plan routing. It does
**not** own per-repository cleanup decisions or execution; those belong to `repo-hygiene` and
`source-control`.
The collector produces the detailed read-only report described below, including the compact
machine-readable rollup and action-plan artifact. Tell the user to run
`/repo-fleet-hygiene:apply --plan-file <path>` (dry-run by default; `--apply` plus confirmation or
`--yes` to mutate; `--remote-branches` additionally opts in to per-branch remote deletion, which
`--yes` never answers). Do not add an execute flag to this audit script.
## Non-negotiable boundary
Never run or suggest running inline from this skill: `git fetch`, `git worktree prune`,
`git worktree repair`, `git worktree remove`, `git branch -d/-D`, `git remote set-url`, or any
filesystem deletion. The bundled script has no mutation mode. `--apply-plan` is a read-only
dry-run approval artifact over a prior plan file. A report may name a command/tool as a future
handoff; the fleet action plan lists those invocations once per repository behind one confirmation
gate. Actual fleet mutation belongs to `/repo-fleet-hygiene:apply`, not this skill.
## Input resolution
Parse `$ARGUMENTS` as opaque arguments for the bundled script. Supported flags:
- `<dir>`: a bare positional path, treated as `--root`. A drive root (`D:`, `D:/`) is a legitimate
discovery root and normalizes to `D:/`. This is the form `/repo-fleet-hygiene:audit D:` uses.
- `--root <dir>`: bounded recursive repository discovery (repeatable).
- `--repo <dir>`: exact repository/worktree target (repeatable).
- `--config <file>`: explicit Git-format config (at most one).
- `--canonical <github.com/owner/repo=path>`: invocation-specific canonical checkout override
(repeatable; explicit wins over config).
- `--skip <name>`: discovery directory-name skip (repeatable). Explicit `--skip` / `fleet.skip`
entries **replace** the default skip list rather than appending. Otherwise shrinking is
impossible. Default (neither CLI nor config): `vendor` plus the package-manager cache trees
`node_modules`, `.venv`, `.pnpm-store`, `.yarn`, `.npm`, `.cargo`, `.rustup`, `.gradle`, `.m2`,
`.nuget`, `__pycache__`, and `.tox`. To shrink (e.g. reach a repo under `vendor/`), list only the
names you still want skipped. CLI and config compose additively with each other like other scope
inputs. Values must be bare directory names (no empty value, no path separator). `.`, `..`, and
`.git` stay skipped unconditionally even when an explicit list omits them.
- `--extend-skip <name>`: discovery directory-name skip that **adds** to whichever list is in
effect (the defaults, or an explicit `--skip` / `fleet.skip` list) instead of replacing it
(repeatable). Config equivalent: repeatable `fleet.skipAppend`. Same bare-name validation.
- `--max-depth <1..12>`: discovery bound; explicit wins over config/default `5`.
- `--project-dir <dir>`: the session's project directory, used for the project-scoped config rung.
It is **not** a scope fallback; see the no-scope ladder below.
- `--detail`: emit collapsed per-target evidence after the rollup (default is rollup + action plan
only).
- `--plan-file <path>`: write the machine-readable action-plan JSON to this path (otherwise a temp
file is created and named in the report).
- `--apply-plan <path>`: standalone read-only mode. Render the ordered dry-run approval artifact
for a previously written plan (cannot combine with discovery flags).
Always pass `--project-dir "${CLAUDE_PROJECT_DIR}"` on audit runs (not on `--apply-plan`). That
variable is substituted in this markdown content and in `allowed-tools` Bash rules, but it is
**not** present in the Bash tool's environment, so the script cannot read it for itself. Passing
it in is what makes the project rung below reachable at all.
If no explicit scope and no config-supplied `fleet.root`/`fleet.repo` resolve, the run uses the
shared no-scope ladder: `--named` paths, then `ghq root --all` when `ghq` is installed, then the
nearest of the 4 parents above the working directory's checkout (above the working directory itself
outside a checkout) that directly holds 2 or more Git repositories, so a run inside one checkout
covers its sibling fleet, then the checkout holding the working directory, else exit 3 naming every
rung. A Git checkout in the working directory is a rung; the session project directory is not one
on its own. **Claim:** `ghq root --all` prints every configured root. **Basis:** the
[ghq README](https://github.com/x-motemen/ghq#usage) (`ghq root [--all]`; "Without '--all' option,
the primary one is shown") and `ghq help root` on ghq 1.10.1 ("--all Show all roots"). **As of:**
2026-09-29. **Recheck:** the README's `root` entry drops or renames `--all`. Pass that guidance
through rather than re-deriving a root yourself. Config resolution is the script's own ladder. Do not
pre-resolve or pass a probed path yourself:
explicit `--config` wins, else the script probes
`<project-dir>/.claude/repo-fleet-hygiene.conf` (project-scoped), else
`~/.claude/repo-fleet-hygiene.conf` (user-global, a machine-scoped fleet config placed there is
recorded user intent, not a guessed root). The report header names the consumed config and its
source, or states that none was consumed. Never guess a broader machine root from the current
path beyond that ladder.
Config-supplied scope is **additive** to CLI-supplied scope: a `--repo X` run still walks every
configured root. The header's `Scope:` line names each contributing rung and its entry count, so
report that line rather than assuming the arguments were the whole scope.
Before execution, reject any arguments outside this grammar, noting that a bare positional path
**is** in the grammar, so `/repo-fleet-hygiene:audit D:` and
`/repo-fleet-hygiene:audit /path/to/tree` are valid invocations to pass through, not arguments to
refuse. What stays rejected is an unrecognized flag: anything beginning with `-` that is not listed
above. Pass every path/override as a quoted argument; never assemble a shell fragment from config,
repository, remote, or branch text.
Run exactly once:
```bash
${CLAUDE_SKILL_DIR}/scripts/audit-fleet.sh --project-dir "${CLAUDE_PROJECT_DIR}" <validated-and-quoted-arguments>
```
The script validates config with `git config --file`; it never sources or executes it.
## Evidence rules
The bundled collector is authoritative for classifications. Preserve its evidence in the report:
1. **Canonical checkout:** explicit remote-keyed override → configured remote-keyed override → the
repository's **main worktree**, read as the first record of `git worktree list --porcelain`
(which lists the main worktree first regardless of where it runs). `git rev-parse
--show-toplevel` alone cannot identify a canonical checkout: inside a linked worktree it returns
the linked root, so a sibling worktree reached first by discovery would otherwise become the
path every handoff points at. When a supplied or discovered path resolves to a different main
worktree, the header states the substitution on one `Resolved to main worktree:` line per
repository, naming every path that resolved into it. Relay it, because the operator named one
path and the report is about another. The report always shows
discovered and canonical paths. An
override target with a missing/non-GitHub remote, or a different identity that cannot be proven to
resolve to the same GitHub repository, stops that repository's local audit before evidence combines.
2. **GitHub identity:** read the selected fetch remote with `git remote get-url`; accept only
`github.com/owner/repo`; query `GET /repos/{owner}/{repo}`. If returned `full_name` differs, report
`HIGH` transfer/rename evidence and **continue** branch/worktree analysis against that resolved
identity. A moved remote is not a reason to skip local classification or merge evidence. A
404/403/network error is `UNKNOWN`, never "deleted" or "moved". A 404/403 on an identity listed
in `fleet.ackUnavailable` is demoted to `ACKNOWLEDGED`, still reported, never suppressed; acks
never touch non-404/403 failures or successful-response evidence.
3. **Merged branch:** one aliased `gh api graphql` query per repository page of local branches
(up to `MERGED_PR_GRAPHQL_ALIAS_PAGE` `headRefName` aliases per call, `first:1`,
`states:[MERGED]`). GraphQL's `headRefName` argument is an **exact** match, never the search
API's prefix-matching `head:` qualifier, so `feature/auth` and `feature/auth-v2` never conflate.
Measured rate cost stays 1 per call (nodeCount equals the alias count); that stays well under
GitHub's documented 500,000-node ceiling and 5,000-point/hour primary limit. There is no merged-PR
result window and no per-branch fallback: every non-default local branch the operator asked
about is queried by exact name, including heads GitHub auto-deleted and a later fetch pruned. Fail closed when `gh`/GraphQL is
unavailable. Emit `github-pr-evidence-unavailable` and never infer unmerged from a missing
row after a failed page. Identical branch names in another repository are unrelated. `HIGH`
requires the PR `headRefOid` to equal the current local tip. Tip drift is `MEDIUM` manual
review. Git ancestry without GitHub evidence is `LOW` and never called merged-by-PR, and
under squash merges that ancestry predicate is near-inert, so on a squash-merging fleet
GitHub evidence is effectively the only merge evidence.
4. **Merged remote branch:** after local classification, the same merged-PR rows are matched
against each remote-tracking tip under the selected remote. When `headRefOid` equals that tip
and the branch is not the default, probe live existence with
`git ls-remote --heads <remote> refs/heads/<branch>`. A matching tip → `HIGH`
`merged-remote-branch` (remote head still present after merge, unset or blocked
`delete_branch_on_merge`). ls-remote failure → `MEDIUM` cached observation (may be stale after a
prune-less fetch). Empty ls-remote → no finding (head already gone upstream). Unlike every
other probe, ls-remote keeps global and system Git config, where the transport lives
(`core.sshCommand`, `credential.helper`, proxies). It runs only when the remote URL names the
same github.com repository with and without that config; otherwise it counts as a failure.
When every attempted probe in the run fails (non-zero status; empty success does not count),
emit one fleet-level `UNKNOWN` `ls-remote-fleet-unavailable` finding and withhold the
per-repository `MEDIUM` rows, because no live probe succeeded.
Remote-only heads (local already deleted) are included. The handoff is an optional `git push --delete --dry-run`
preview naming the remote and branch; this skill never runs it and never calls org-admin APIs to
flip repository settings. Enabling `delete_branch_on_merge` is complementary (it stops the class
accruing) and is **not** a substitute for this fleet visibility.
When the merged PR's `headRefOid` differs from the remote-tracking tip, the tip is still merged
content if it is an ancestor of `headRefOid` and that commit is in the clone
(`git merge-base --is-ancestor <tip> <headRefOid>` exits 0). That case is `MEDIUM`
`merged-remote-branch` with the reason `tip is an ancestor of the merged head`. It runs no
ls-remote probe, so it is never `HIGH`. A head commit absent from the clone, or a tip that is not
an ancestor, emits nothing.
**Remote branch families:** each remote-tracking branch under the selected remote whose name
belongs to a family below, and that no `merged-remote-branch` finding covers, gets one `LOW`
`remote-branch-family` row: the family, the tip, its age in days from the committer date, and
whether the tip is an ancestor of `<remote>/<default>` (`yes`, `no`, or `unknown`). The row reads
the last-fetched `refs/remotes/<remote>/` inventory and adds no remote probe. It is report-only:
no handoff, no deletion, no deletion preview. Whether and when such branches are deleted is the
owner's decision.
| Family | Branch name | Source |
|---|---|---|
| `agent` | `agent-` followed by hex digits only | Claude Code subagent worktrees |
| `claude` | `claude/*` | Claude Code on the web |
| `plan` | `plan/*` | named by hand |
| `stranded` | `stranded/*` | named by hand |
| `pre-wipe` | `pre-wipe/*` | ad hoc safety pushes made before a reimage |
| Claim | Basis | As of | Recheck |
|---|---|---|---|
| `agent-<hex>` branches come from Claude Code subagent worktrees. | `plugins/source-control/scripts/worktree-create.sh` uses the harness-supplied name verbatim as the branch. | 2026-09-29 | The harness stops supplying `agent-<hex>` names, or that script changes how it names the branch. |
| `claude/*` branches come from Claude Code on the web. | Observed on fleet remotes; <https://code.claude.com/docs/en/claude-code-on-the-web> names no branch prefix. | 2026-09-29 | That page names a branch prefix, or a `claude/*` branch turns up with another origin. |
**Unmerged remote branch:** a remote-tracking head with no `MERGED` PR row (not the default,
current, or worktree-attached branch) gets one more read-only GraphQL query for pull requests
whose `headRefName` is exactly that branch, in any state, plus `totalCount`. With a live
`ls-remote` tip: no PR at all and a complete list → `HIGH` `unmerged-remote-branch`, class
`never-pr`; only `CLOSED` PRs with one `headRefOid` equal to the live tip → `HIGH`, class
`closed-unmerged`. An `OPEN` or `MERGED` PR emits nothing. `ls-remote` failing → `MEDIUM`
`unmerged-remote-branch-unverified` (no tip, no deletion candidate). Only `CLOSED` PRs and none at
the live tip (commits pushed after close), or a `totalCount` above the returned page → `MEDIUM`
`unmerged-remote-branch-review`. Only the two `HIGH` classes reach the action plan, as
`delete-remote-branches` rows carrying the live tip as `expected_oid`, the remote's `remote_key`,
and the `github_repo` that apply rechecks. A branch whose only PR is merged stays
`merged-remote-branch` and is never a deletion candidate here.
5. **Local inventories:** parse only `git worktree list --porcelain -z` registrations and
NUL-delimited `git for-each-ref` branch/tip records. Directory naming is
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, unlocked registrations with reliable admin emit one `MEDIUM` `worktree-status-handoff`
per repository naming those paths. Disposability (stranded / unknown / safe) is owned by
`/source-control:worktree status`, and this collector emits no `git status`-based substitute
verdict. Separately, every linked worktree that passes existence and root-verifiability checks
is classified against its own repository's configured worktree root (`worktreeroot.path` read
with `git -C <canonical>`, which honors `includeIf`, else the project directory's key, then
source-control `worktree_root`):
conforming, outside/wrong-layout
(expected `<root>/<owner>-<repo>-<slug>` or `<root>/<repo>-<slug>` without origin, matching
`/source-control:worktree create`; create-shaped basenames stay conforming after branch
rename/detach; comparisons use physical paths so symlink aliases of the configured root do not
false-positive), or tool-owned (Codex/Cursor).
Missing, prunable, non-root, and root-unverifiable registrations keep their own finding kinds and
are excluded from conformance denominators. When no root is configured, placement is reported
without asserting a convention. A repository with no `worktreeroot.path` of its own falls back
to the project directory's key, then pluginConfigs; it never borrows another target's root. The
header names the first resolvable root. The fleet summary lists each distinct root with its
repository count when repositories resolve different roots, and lists each repository whose own
root differed, with the root it used. Advice for a repository's worktrees names that
repository's root. If pluginConfigs cannot be read because `jq` is missing, emit
`worktree-root-pluginconfigs-unreadable` rather than pretending the key is unset.
Per-repository and fleet rollups always state the classifiable counts. 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.
6. **Protection:** current/default/worktree-attached branches are never emitted as standalone branch
cleanup candidates. A merged worktree is routed to worktree dry-run first. `merged-remote-branch`
is independent of local attachment. It describes the remote ref.
Every emitted finding kind, both confidence axes, and the merge-strategy and
`gc.worktreePruneExpire` dependencies the tiers rest on:
[reference/confidence-model.md](reference/confidence-model.md). The official Git/GitHub behaviors
this collector relies on: [reference/official-sources.md](reference/official-sources.md). The
read-only enforcement model and its threat assumptions:
[reference/security-review.md](reference/security-review.md).
## Presentation
Default output is screen-scale:
1. Fleet header (config, scope, discovery counts).
2. **Repository rollup**. One row per repository with `CLEAN` / `N candidates` /
`BLOCKED (evidence gap)`, plus counts by finding kind. Every UNKNOWN kind except the
disclosure-only `discovery-skip` and `discovery-symlink-skip` makes a repository, and so the fleet,
BLOCKED. Fleet-level findings (stale config,
duplicate checkouts) get their own row. A fleet verdict summarizes blocked vs candidate vs clean.
3. **Fleet action plan**. Recommended skill invocations **once per repository** (not once per
finding), ordered so branch cleanups precede worktree cleanups, behind **one** confirmation gate.
4. Path to the machine-readable action-plan JSON (and the `--apply-plan` dry-run invocation).
Pass `--detail` when the operator needs evidence: targets are collapsed (one entry per path/branch
carrying every applicable finding), never duplicated across confidence groups. Never collapse
same-named branches across repositories.
`ACKNOWLEDGED` is a prominence demotion, not a fifth confidence tier: the evidence stays exactly
as weak as the `UNKNOWN` it came from. A rollup `CLEAN` verdict means no actionable cleanup-plan
candidates (the kinds that produce skill invocations) and no UNKNOWN evidence gap for that
repository (`discovery-skip` and `discovery-symlink-skip` are disclosure-only and do not count), not
"GitHub was unreachable so nothing was wrong." Manual-review HIGH/MEDIUM findings
(for example `locked-worktree` or `merged-pr-tip-drift`) remain in kind counts but do not inflate
`N candidates` when the action plan correctly lists `Actions: none`.
When the user wants to act on a fleet report, tell them to run
`/repo-fleet-hygiene:apply --plan-file <path>` (dry-run, then `--apply`) rather than driving
per-repository skills by hand.
## Fleet cleanup plan
This section defines how audit relates to the execute verb; it does not add a mutation command to
the audit argument grammar.
The cleanup-plan consumer is `/repo-fleet-hygiene:apply`. It takes only the machine-readable
rollup artifact. Never parse this skill's human report into executable operations. The apply
verb:
1. rejects an incomplete, invalid, or non-audit artifact and preserves every repository-qualified
target, confidence, evidence gap, and disposition;
2. owns batched merged-local-branch deletion (with fail-closed OID refresh) and worktree cleanup in
plan order rather than widening this audit script;
3. presents one fleet action plan and obtains one explicit confirmation (or `--yes`) before any
mutation; and
4. re-derives mutable facts, including relevant branch/worktree OIDs, at execution time. An old
artifact is evidence, not authorization.
The rollup, the `--apply-plan` dry-run, and `/repo-fleet-hygiene:apply` all ship. Do not
invent `--cleanup-plan`, `--execute`, or report-and-execute behavior on `audit-fleet.sh`. Return the
report, rollup, and plan path; tell the user to run `/repo-fleet-hygiene:apply` for execution. A
`HIGH` evidence tier is never itself permission to delete a branch or worktree.
Related fleet contracts that remain separate:
- merged remote branches carry a distinct safety gate: this skill reports them, and their deletion
is not part of `/repo-fleet-hygiene:apply`. Only never-PR and closed-unmerged remote branches can
be deleted, and only through `/repo-fleet-hygiene:apply --remote-branches` (per-branch prompt,
never bypassed by `--yes`, tip recorded first).
## Graceful degradation
- Git missing or too old: stop before scanning and give the prerequisite error.
- Invalid config SYNTAX, invalid override, or an invalid CLI-supplied `--repo`/`--root` path: report
the exact invalid input and stop; never silently fall back.
- No scope given and no rung of the no-scope ladder resolves (exit 3): stop, and relay the script's
remedy block verbatim. The operator did not choose that path, so the rejection alone is not
actionable.
- A config-sourced `fleet.repo`/`fleet.root` path that is missing or not a Git working tree degrades
per-entry, not per-run: the entry becomes an `UNKNOWN` `stale-config-entry` finding and the rest of
the fleet is still audited (deleting repositories right after an audit must not abort every
subsequent run until the config is edited).
- A path discovered under `--root` that is unreadable or not a Git working tree (despite a `.git`
marker) degrades the same way: an `UNKNOWN` `discovery-skip` finding, header skip counts, and the
rest of the fleet is still audited. The finding is disclosure-only: it does not make the fleet
BLOCKED. An explicitly named `--repo` that is not a working tree still
hard-fails.
- A directory that itself carries a `.git` marker (directory or file) is treated as a nested
repository: discovery `add_target`s it and **returns without descending into its children**. A
repository buried inside another repository's working tree therefore never appears as its own
audit target unless named explicitly via `--repo` / `fleet.repo`.
- A symlinked or junctioned intermediate directory under `--root` is not followed, but is disclosed
as an `UNKNOWN` `discovery-symlink-skip` finding and counted on the discovery-skips header line.
Like `discovery-skip`, it is disclosure-only and does not make the fleet BLOCKED. Windows
directory junctions test as symlinks under Git Bash, so they take this path. Symlinked
discovery *roots* remain a hard refusal (CLI) or `stale-config-entry` (configured).
- `gh` missing/unauthenticated or API/timeout failure: continue Git/worktree checks, report GitHub
evidence as `UNKNOWN`, and make no merged/migration claim. Compatible `timeout`/`gtimeout` is
preferred; otherwise use the collector's finite TERM-to-KILL Bash watchdog.
- Non-GitHub or ambiguous remote: continue local checks; GitHub identity/PR evidence is `UNKNOWN`.
- Canonical override is missing a GitHub remote or does not resolve to the same normalized GitHub
identity as the discovered repo: surface `UNKNOWN`, stop that repository, and do not merge evidence.
- Worktree porcelain fails: surface `UNKNOWN` and stop local branch/worktree classification for that
repository so no branch can be mislabeled unattached.
- Branch enumeration fails or is malformed: discard every partial record, surface `UNKNOWN`, stop
branch classification, and exclude the repository from the successful-audit count.
## Integration
| Finding | Handoff (not executed here) |
|---|---|
| `merged-local-branch` | Run `/repo-hygiene:clean git` in the named canonical repository |
| `merged-remote-branch` | Optional preview only: `git push --delete --dry-run <remote> <branch>` in the canonical repository (never executed here). Enabling GitHub `delete_branch_on_merge` is complementary and owned by the repository's settings automation. This audit does not change it |
| `unmerged-remote-branch` | Gated delete: `apply-plan.sh --plan-file <path> --apply --remote-branches` in the operator's own terminal, one prompt per branch, deleted only at the recorded tip; `/repo-fleet-hygiene:apply --remote-branches` from a session without a terminal only previews. Preview by hand: `git push --delete --dry-run <remote> <branch>` in the canonical repository |
| `unmerged-remote-branch-unverified` | Confirm `git ls-remote --heads` works by hand, then rerun. No deletion is planned |
| `unmerged-remote-branch-review` | Manual review of the branch and its closed PRs; never a deletion candidate |
| `remote-branch-family` | None. Report only: no deletion and no deletion preview. Read the family, age and on-default fields and decide by hand |
| `merged-worktree`, `prunable-worktree`, `missing-worktree` | Run `/source-control:worktree cleanup --dry-run` in the canonical repository |
| `worktree-status-handoff` | Run `/source-control:worktree status` in the canonical repository (stranded-work axis); use cleanup `--dry-run` only after Work is safe. If `source-control` is not installed, name the listed worktree targets and the missing collaborator. Emit no porcelain-based substitute verdict |
| `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-nested-in-repository` | Recreate at an external root with `/source-control:worktree create`, then remove the nested one |
| `worktree-outside-configured-root` | Recreate at the expected location named in evidence with `/source-control:worktree create`, then remove the misplaced one |
| `worktree-wrong-layout` | Recreate at the expected `<owner>-<repo>-<slug>` path under the configured root, then remove the wrong-layout one |
| `worktree-tool-owned` | Leave to Codex/Cursor lifecycle, or migrate deliberately to the configured root |
| `worktree-root-conformance` | Read the per-worktree outside/wrong-layout findings for expected paths; migrate toward the configured root |
| `worktree-root-conformance-summary` | Same as per-repository conformance; fleet-scale migration toward the configured root |
| `worktree-root-unconfigured` | Set `worktreeroot.path` (git config) or source-control `worktree_root`, then rerun |
| `ls-remote-fleet-unavailable` | Confirm `git ls-remote --heads` works by hand with the operator's usual Git transport, then rerun |
| `worktree-root-pluginconfigs-unreadable` | Install `jq`, or set `worktreeroot.path`; do not treat the fleet as unconfigured |
| `worktree-placement-unverifiable` | Inspect the canonical checkout; placement was not checked for any of its worktrees, so their placement is unknown rather than confirmed |
| `bare-repo-with-working-tree` | Manual review. `core.bare=true` coincides with working-tree content or registered linked worktrees, so the main worktree is disabled while linked worktrees keep working. Nothing is lost; the documented remedy is `git config --local core.bare false` in the named checkout |
| `github-remote-moved` | Human-reviewed `git remote set-url`; this plugin never changes remotes |
This plugin remains useful if those optional collaborators are absent: the report names the local
Git/GitHub evidence and target so another tool or human can act. For `worktree-status-handoff`, when
`source-control` is missing, name the listed worktree paths and the missing collaborator and do not
invent a porcelain-based disposability substitute.
## Gotchas
- **Which config is consumed depends on where the audit runs.** Config resolution follows the ladder in
the Input resolution section: explicit `--config`, else the project-scoped
`.claude/repo-fleet-hygiene.conf`, else the user-global one. A project-scoped config is invisible when
the audit runs from a different project, so confirm the consumed config named in the report header
before trusting a run's scope.
- **Config paths resolve relative to the config file's directory.** A relative `root`/`repo`/canonical
path is anchored at the config directory, not the audit's working directory. Absolute paths work but
are what a consumer's write-time path guard flags, so author config via
`/repo-fleet-hygiene:setup apply`, which prefers the portable relative form.