okhp3-repository-janitor
OverKill Hill P³ · overkillhill.com · github.com/OKHP3
Keep a multi-repository Git estate understandable without losing work. Treat origin/main as the comparison baseline, not as evidence that other work is disposable.
Scope
Audit explicitly named roots and origin identities. Python 3.9+ and a Git
version supporting the documented safe-fetch flags are required for the helper.
Missing origin/main is a coverage hold; do not silently substitute another
branch or invent a new main branch.
Safety contract
- Begin with a read-only inventory. Do not delete repositories, branches, stashes, refs, or commits during discovery.
- Preserve before pruning. Record dirty files, stashes, local-only commits, and unreachable commits. Propose dated archive refs as recovery points; creating them requires explicit recovery authorization and is never part of audit-only discovery.
- Refresh remotes only when requested, using the helper's explicit no-prune, no-prune-tags, no-auto-maintenance fetch. Git configuration can enable pruning even without a command-line prune flag. Do not permit it during inventory.
- Compare each checkout to the refreshed
origin/main. Report both file-level and commit-level differences.
- Treat a merged branch, a closed pull request, and an abandoned branch as different facts. Query the pull request before deletion.
- Merge only a reviewed pull request whose checks and destination are suitable. Do not merge a branch merely because it is old or has a bot name.
- Delete a remote branch only when its merged/superseded status, commit reachability, and pull-request state are confirmed. Delete the local tracking branch only after the remote deletion is verified.
- Never rewrite
main, force-push, or remove stashes, archive refs, or untracked files without a separately stated and confirmed recovery plan.
Audit workflow
- Resolve the mirror root and inventory every child, including skipped, invalid and inaccessible paths. Report linked worktrees outside the root as coverage holds, not as inspected. Keep their files unchanged. Do not follow directory symlinks outside the root.
- Run
scripts/audit_mirrors.py <mirror-root> --include-unreachable for a read-only baseline. Add --fetch to refresh remote-tracking refs without changing any working tree.
- For each repository, inspect the report sections in this order:
- dirty working-tree files and stashes;
- commits reachable locally but not from a remote;
- current
HEAD versus origin/main, distinguishing its direct tree difference from the files changed on the branch since the shared base;
- local and remote branches and whether each is already reachable from
origin/main;
- archive refs and unreachable commits.
- Query GitHub for every non-main remote branch: its pull request, whether it is open, closed, or merged, its checks, and whether a newer branch supersedes it. Read
references/branch-lifecycle.md before classifying candidates.
- Produce a decision ledger with one row per candidate:
keep, review, merge, close PR, archive, or delete. State the evidence and recovery point.
- Stop here in audit mode. Execution is a separate user-authorized phase, never an implicit continuation of the inventory.
Authorized lifecycle execution
Read the current action grant before each batch. For each approved merge,
refresh, verify the expected head, inspect the reviewed diff and required checks,
merge through the pull request without bypass, and refetch. Delete only the
separately authorized exact branch after verifying containment, PR state and all
linked-worktree use. Re-run the audit after every batch. Never manufacture merges
or squashes when fast-forward parity suffices. Patch-equivalent historical work
may be superseded, but a closed PR or different commit hash does not prove it.
Require a conditional expected-tip operation for remote deletion; rereading a
branch before deletion does not protect a concurrent push. If unavailable, do
not delete. Default schedules never execute lifecycle mutations.
Scheduled operation
Use audit-only mode for a daily task:
python3 scripts/audit_mirrors.py MIRROR_ROOT --fetch --include-unreachable --output PRIVATE_STATE/current.json
Add --previous PRIVATE_STATE/last-successful.json when a compatible previous
snapshot exists. Inspect the process exit code and coverage errors before
advancing that baseline. Preserve partial attempts separately; an error or empty
discovery cannot count as clean. Use the output to report new, changed and
resolved exceptions. Missing or mismatched baselines are explicit gaps.
Partial snapshots may be compared for new/changed observations, but cannot
establish resolution. Keep the last successful baseline separate from the last
attempt so persistent coverage holds do not cause duplicate unchanged alerts.
A weekly task may prepare a decision ledger and inspect open PRs, but must not
merge, close, delete, pin refs, commit or push. Coordinate recurring incident
repairs through okhp3-github-notification-maintainer and its explicit schedule
grant. Never store raw private reports inside public repositories.
Output contract
Report:
- coverage: discovered repositories, skipped paths, linked-worktree boundaries, Git command/fetch failures, missing
origin/main, and snapshot compatibility;
- preservation holds: dirty files, stashes, local-only commits, unreachable commits, and archive refs;
- variants: per-repository commit divergence and changed file paths versus
origin/main;
- branch lifecycle: each non-main branch, PR state, reachability, recommendation, and rationale;
- actions taken: exact merge, close, archive, or deletion targets, plus verification;
- remaining decisions: targets that need human intent or a deeper code review.
Record the host/root and unobserved computers or agent workspaces. Full SHAs are
identity; abbreviated display hashes are not adequate for mutation gates. Saved
stashes and archive refs remain exceptions even when every checkout is clean
and HEAD equals origin/main. A local-only historical commit may already be
patch-equivalent to published work; inspect before claiming loss or redundancy.
Both archive_refs and archive_ref_shas use full refs/archive/... names.
Version 0.2.0 shortened names in archive_refs; consumers of those earlier
snapshots must normalize that display list before joining it to the SHA map.
The exception comparison uses the unchanged full-name SHA map.
Run the package's temporary-repository regression suite with
python3 -m unittest discover -s tests -v. Passing it verifies tested inventory
behavior, not an unseen cross-machine estate or autonomous cleanup safety.
Do not collapse ambiguity into a cleanup recommendation. A clean report is one with explicit exceptions, not necessarily zero branches.
Resources
scripts/audit_mirrors.py — read-only or fetch-only multi-checkout inventory with JSON output.
references/branch-lifecycle.md — evidence required for merge, retention, archival, and deletion decisions.
About
Built by Jamie Hill · OverKill Hill P³
Published at github.com/OKHP3
Part of the OKHP3/skillz Agent Skill library.
MIT License -- free to use, fork, and adapt. A nod to the source is appreciated.
1---2name: okhp3-repository-janitor3description: Reconcile a collection of local Git repositories with their GitHub origins, inspect all local variations against origin/main, preserve uncommitted or unreachable work, and manage branch lifecycle. Use when a user has multiple clones or computers, needs to find differences from origin/main, recover forgotten work, review agent/Copilot/Dependabot branches, merge completed pull requests, prune verified redundant branches, or establish a repeatable daily or weekly repository-maintenance routine.4license: MIT5---67# okhp3-repository-janitor89**OverKill Hill P³** · [overkillhill.com](https://overkillhill.com) · [github.com/OKHP3](https://github.com/OKHP3)1011Keep a multi-repository Git estate understandable without losing work. Treat `origin/main` as the comparison baseline, not as evidence that other work is disposable.1213## Scope1415Audit explicitly named roots and origin identities. Python 3.9+ and a Git16version supporting the documented safe-fetch flags are required for the helper.17Missing origin/main is a coverage hold; do not silently substitute another18branch or invent a new main branch.1920## Safety contract21221. Begin with a read-only inventory. Do not delete repositories, branches, stashes, refs, or commits during discovery.232. Preserve before pruning. Record dirty files, stashes, local-only commits, and unreachable commits. Propose dated archive refs as recovery points; creating them requires explicit recovery authorization and is never part of audit-only discovery.243. Refresh remotes only when requested, using the helper's explicit no-prune, no-prune-tags, no-auto-maintenance fetch. Git configuration can enable pruning even without a command-line prune flag. Do not permit it during inventory.254. Compare each checkout to the refreshed `origin/main`. Report both file-level and commit-level differences.265. Treat a merged branch, a closed pull request, and an abandoned branch as different facts. Query the pull request before deletion.276. Merge only a reviewed pull request whose checks and destination are suitable. Do not merge a branch merely because it is old or has a bot name.287. Delete a remote branch only when its merged/superseded status, commit reachability, and pull-request state are confirmed. Delete the local tracking branch only after the remote deletion is verified.298. Never rewrite `main`, force-push, or remove stashes, archive refs, or untracked files without a separately stated and confirmed recovery plan.3031## Audit workflow32331. Resolve the mirror root and inventory every child, including skipped, invalid and inaccessible paths. Report linked worktrees outside the root as coverage holds, not as inspected. Keep their files unchanged. Do not follow directory symlinks outside the root.342. Run `scripts/audit_mirrors.py <mirror-root> --include-unreachable` for a read-only baseline. Add `--fetch` to refresh remote-tracking refs without changing any working tree.353. For each repository, inspect the report sections in this order:36 - dirty working-tree files and stashes;37 - commits reachable locally but not from a remote;38 - current `HEAD` versus `origin/main`, distinguishing its direct tree difference from the files changed on the branch since the shared base;39 - local and remote branches and whether each is already reachable from `origin/main`;40 - archive refs and unreachable commits.414. Query GitHub for every non-main remote branch: its pull request, whether it is open, closed, or merged, its checks, and whether a newer branch supersedes it. Read `references/branch-lifecycle.md` before classifying candidates.425. Produce a decision ledger with one row per candidate: `keep`, `review`, `merge`, `close PR`, `archive`, or `delete`. State the evidence and recovery point.436. Stop here in audit mode. Execution is a separate user-authorized phase, never an implicit continuation of the inventory.4445## Authorized lifecycle execution4647Read the current action grant before each batch. For each approved merge,48refresh, verify the expected head, inspect the reviewed diff and required checks,49merge through the pull request without bypass, and refetch. Delete only the50separately authorized exact branch after verifying containment, PR state and all51linked-worktree use. Re-run the audit after every batch. Never manufacture merges52or squashes when fast-forward parity suffices. Patch-equivalent historical work53may be superseded, but a closed PR or different commit hash does not prove it.54Require a conditional expected-tip operation for remote deletion; rereading a55branch before deletion does not protect a concurrent push. If unavailable, do56not delete. Default schedules never execute lifecycle mutations.5758## Scheduled operation5960Use audit-only mode for a daily task:6162```text63python3 scripts/audit_mirrors.py MIRROR_ROOT --fetch --include-unreachable --output PRIVATE_STATE/current.json64```6566Add `--previous PRIVATE_STATE/last-successful.json` when a compatible previous67snapshot exists. Inspect the process exit code and coverage errors before68advancing that baseline. Preserve partial attempts separately; an error or empty69discovery cannot count as clean. Use the output to report new, changed and70resolved exceptions. Missing or mismatched baselines are explicit gaps.71Partial snapshots may be compared for new/changed observations, but cannot72establish resolution. Keep the last successful baseline separate from the last73attempt so persistent coverage holds do not cause duplicate unchanged alerts.7475A weekly task may prepare a decision ledger and inspect open PRs, but must not76merge, close, delete, pin refs, commit or push. Coordinate recurring incident77repairs through `okhp3-github-notification-maintainer` and its explicit schedule78grant. Never store raw private reports inside public repositories.7980## Output contract8182Report:8384- coverage: discovered repositories, skipped paths, linked-worktree boundaries, Git command/fetch failures, missing `origin/main`, and snapshot compatibility;85- preservation holds: dirty files, stashes, local-only commits, unreachable commits, and archive refs;86- variants: per-repository commit divergence and changed file paths versus `origin/main`;87- branch lifecycle: each non-main branch, PR state, reachability, recommendation, and rationale;88- actions taken: exact merge, close, archive, or deletion targets, plus verification;89- remaining decisions: targets that need human intent or a deeper code review.9091Record the host/root and unobserved computers or agent workspaces. Full SHAs are92identity; abbreviated display hashes are not adequate for mutation gates. Saved93stashes and archive refs remain exceptions even when every checkout is clean94and HEAD equals origin/main. A local-only historical commit may already be95patch-equivalent to published work; inspect before claiming loss or redundancy.9697Both `archive_refs` and `archive_ref_shas` use full `refs/archive/...` names.98Version 0.2.0 shortened names in `archive_refs`; consumers of those earlier99snapshots must normalize that display list before joining it to the SHA map.100The exception comparison uses the unchanged full-name SHA map.101102Run the package's temporary-repository regression suite with103`python3 -m unittest discover -s tests -v`. Passing it verifies tested inventory104behavior, not an unseen cross-machine estate or autonomous cleanup safety.105106Do not collapse ambiguity into a cleanup recommendation. A clean report is one with explicit exceptions, not necessarily zero branches.107108## Resources109110- `scripts/audit_mirrors.py` — read-only or fetch-only multi-checkout inventory with JSON output.111- `references/branch-lifecycle.md` — evidence required for merge, retention, archival, and deletion decisions.112113## About114115Built by [Jamie Hill](https://overkillhill.com) · [OverKill Hill P³](https://overkillhill.com)116Published at [github.com/OKHP3](https://github.com/OKHP3)117Part of the [OKHP3/skillz](https://github.com/OKHP3/skillz) Agent Skill library.118MIT License -- free to use, fork, and adapt. A nod to the source is appreciated.