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

5.4 KiB

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.