repo-hygiene-audit-skill/repo-hygiene-audit/references/check-catalog.md
Travis Herbranson 236616514f Add repo-hygiene-audit skill (Phase 7: repeatable hygiene sweep)
Read-only scripted audit across homelab/pbs/docker/root domains:
git auditability + .git ownership/dubious, .env perms (0660/devprojects),
inline compose secrets, non-git dirs, uv-init stubs, compose naming,
master-vs-main default, dirty trees, worktrees, herbygitea residue, doc
presence. Emits text/json/md findings mapped to remediation-plan phases.

SKILL.md + scripts/audit.py (stdlib-only) + references + packed .skill.
First run: 39 repos, 62 findings, ruff-clean.
2026-05-22 23:11:48 -04:00

77 lines
5.4 KiB
Markdown

# Check catalogue
Every check `scripts/audit.py` runs, what it looks for, the finding it emits,
and why it matters. All checks are read-only. Each finding carries a `check`
id (stable, dedup-friendly), a `category`, a `severity`, a `detail`, and a
remediation `hint`.
The conventions these checks enforce come from the repo-hygiene-remediation
plan's *Locked Decisions*: `.env` target mode **0660** + group **devprojects**;
secrets live in `.env`, not inline in compose; README = human onboarding,
CLAUDE.md = concise agent guide (not a README clone); docker compose stacks get
README only; prefer `main` over `master`; GitHub-destined repos are pushed
manually.
## AUDITABILITY (maps to Phase 3 — unblock un-auditable repos)
| check id | Looks for | Severity | Why |
|----------|-----------|----------|-----|
| `no-git` | A directory with real (non-dotfile) content but no `.git` | high | Can't be diffed, reviewed, or doc-audited until it's under version control. |
| `no-commits` | A `.git` exists but `HEAD` has zero commits | high | Everything is untracked; needs an initial commit pass. |
| `dubious-ownership` | `git` refuses the repo with "detected dubious ownership" | high | Repo is unusable to the current user. Fix ownership, or add a **scoped** `safe.directory <path>` — never the `*` wildcard, which disables the safety check globally. |
| `git-owner-drift` | `.git` owner differs from the directory owner (e.g. travadmin vs herbyadmin) | medium | Symptom of mixed-user operations; precedes dubious-ownership breakage. |
| `group-drift` | Directory group is not `devprojects` | low | Breaks the shared-group access model the dev tree relies on. |
## SECURITY (maps to Phase 2 — security remediation)
| check id | Looks for | Severity | Why |
|----------|-----------|----------|-----|
| `env-world-readable:<file>` | `.env` (or `.env.<env>`) readable by `other` | high | Secrets exposed to any local account. |
| `env-mode:<file>` | `.env` mode not in {0600, 0660} | medium | Doesn't meet the 0660/0600 target. |
| `env-group:<file>` | `.env` group is not `devprojects` | low | Group-access model drift. |
| `inline-secret:<file>` | A compose file assigns a secret-looking key (SECRET/PASSWORD/TOKEN/KEY/JWT/ENCRYPTION/SALT…) to a **literal** value (long hex/base64, not `${VAR}` and not a placeholder) | high | Secrets belong in a gitignored `.env`, not committed in compose. Heuristic — verify before acting; rotate if the repo was ever public. |
`.env.example` / `.env.sample` / `.env.template` / `.env.dist` are ignored
(they're meant to be committed and world-readable).
## CLEANUP (maps to Phase 4 — cross-repo cleanups)
| check id | Looks for | Severity | Why |
|----------|-----------|----------|-----|
| `uv-init-stub` | `main.py`/`hello.py` + `pyproject.toml` + (`uv.lock` or `.python-version`) in a repo with no `src/` and ≤2 `.py` files | low | Classic `uv init` residue left in non-Python repos; not on any build path. |
| `compose-legacy-name` | `docker-compose.yml` present, no `compose.yml` | low | Modern Compose prefers `compose.yml`. |
| `compose-rename-midflight` | Both `docker-compose.yml` and `compose.yml` present | medium | A rename was started but not finished; `git rm` the legacy file. |
| `stale-review-doc` | `GEMINI.md` or `*review*.md` at repo root | low | Stray review artifacts; move into a `reviews/` subdir or delete. |
| `herbygitea-residue` | The string `herbygitea` in any tracked file (the removed SSH alias) | medium | Dead remote/host alias; will break on next use. |
| `stray-dir` | Directory name ending `.broken` / `.backup-<date>` / `.bak` / `.old` | low | Scratch/backup cruft sitting in a domain folder. |
## GIT_STATE (review before commit/merge)
| check id | Looks for | Severity | Why |
|----------|-----------|----------|-----|
| `master-default` | `master` branch exists, no `main` | medium | House convention is `main`. |
| `docs-audit-branch` | A leftover `docs-audit*` branch | low | Residue from the one-time sweep; merge/push then delete. |
| `dirty-tree` | Uncommitted changes in the working tree | low | Sequence the doc/work commit deliberately; don't audit over churn. |
| `extra-worktrees` | More than one attached worktree | info | Possible stale worktrees to prune. |
| `no-upstream` | Current branch has no upstream | info | Expected for unpushed audit work; informational. |
| `unpushed-commits` | Commits ahead of upstream | info | Push when ready (manual for GitHub-destined repos). |
## DOCS (audit follow-up)
| check id | Looks for | Severity | Why |
|----------|-----------|----------|-----|
| `missing-readme` | No `README.md` in a git repo | medium | No human onboarding doc. |
| `missing-claude` | No `CLAUDE.md` in a git repo (skipped for the `docker` domain) | medium | No agent operating guide. Compose stacks intentionally get README only. |
| `claude-md-bloat` | `CLAUDE.md` > 200 lines | low | Likely a README clone — violates the role split (CLAUDE.md should be a concise, gotcha-focused agent guide). |
`claude-md-bloat` and the missing-doc checks flag **candidates** for the
semantic five-dimension review; they can't judge whether the prose is accurate.
## Extending
Add a `check_*(rep: RepoReport) -> None` function that appends `Finding`
objects, then register it in the `CHECKS` list. Keep it read-only and wrap
risky I/O — the orchestrator already catches exceptions per check so one bad
repo can't abort the sweep, but checks should fail soft on their own where they
can.