wiki-vault/wiki-context are an MCP-owned, managed surface — a second scanner risks interfering with how the vault MCP maintains its context. Remove the 'root' domain from DEFAULT_DOMAINS so a plain run no longer touches the vault; the --scan/--repo escape hatch still allows manual one-offs. Updated SKILL.md, check-catalog, findings-format, README, CLAUDE.md with the rationale. Default run: 3 domains, 37 repos, 54 findings (was 61); clears the vault-only false positives (herbygitea doc-mention, wiki-context uv-init stub).
5.7 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.
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 <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.