# 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. ## Scope Default domains: `homelab`, `pbs`, `docker`. The Obsidian vault (`wiki-vault`, `wiki-context`) is **intentionally excluded** — it's an MCP-owned, managed surface, so a second scanner risks interfering with how the vault MCP maintains its context. Use `--scan` / `--repo` to point at it manually if ever needed; don't re-add it to the defaults. ## 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 ` — 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:` | `.env` (or `.env.`) readable by `other` | high | Secrets exposed to any local account. | | `env-mode:` | `.env` mode not in {0600, 0660} | medium | Doesn't meet the 0660/0600 target. | | `env-group:` | `.env` group is not `devprojects` | low | Group-access model drift. | | `inline-secret:` | 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-` / `.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.