# Findings output format Three renderers, one finding model. Pick the format for the consumer: - `text` — terminal triage. Summary line, full `INVENTORY` (every repo, clean ones included), then findings grouped by category. - `md` — phase-mapped markdown. Each finding is a `- [ ]` checkbox under the remediation-plan phase heading it belongs to, so a run pastes straight into the project plan as actionable items. - `json` — machine-readable for week-over-week diffing and automation. ## Finding model Every finding is one record: | Field | Meaning | |-------|---------| | `domain` | `homelab` / `pbs` / `docker` (the Obsidian vault is out of scope by default — see check-catalog.md) | | `repo` | Directory name within the domain | | `path` | Absolute path | | `category` | `SECURITY` / `AUDITABILITY` / `CLEANUP` / `GIT_STATE` / `DOCS` | | `severity` | `high` / `medium` / `low` / `info` | | `check` | Stable check id (e.g. `no-git`, `env-world-readable:.env`) | | `detail` | Human-readable description of what was found | | `hint` | Suggested remediation (informational — the skill does not apply it) | | `key` | `domain/repo:check` — stable identifier for dedup/diffing (JSON only) | The `key` is deliberately stable across runs: a finding that persists keeps the same key, so diffing two JSON runs cleanly separates *new drift* (keys that appeared) from *remediated items* (keys that disappeared). ## Category → phase mapping This is how findings line up with the repo-hygiene-remediation plan, so the recurring review feeds the same backlog structure the original audit used: | Category | Plan phase | |----------|-----------| | `SECURITY` | Phase 2 — Security remediation | | `AUDITABILITY` | Phase 3 — Unblock un-auditable repos | | `CLEANUP` | Phase 4 — Cross-repo cleanups | | `GIT_STATE` | Git state (review before commit/merge) | | `DOCS` | Docs (audit follow-up) | ## JSON shape ```json { "generated": "2026-05-22T23:05:00", "host": "herbys-dev", "repos_scanned": 39, "summary": { "by_severity": {"high": 11, "medium": 19, "low": 23, "info": 9}, "by_category": {"SECURITY": 5, "AUDITABILITY": 9, "CLEANUP": 8, "GIT_STATE": 27, "DOCS": 13} }, "inventory": [ {"domain": "docker", "repo": "authelia", "path": "...", "is_git": false, "owner": "root", "git_owner": "", "group": "root", "branch": "", "finding_count": 2} ], "findings": [ {"domain": "docker", "repo": "authelia", "path": "...", "category": "AUDITABILITY", "severity": "high", "check": "no-git", "detail": "directory holds real content but has no .git", "hint": "git init + initial commit ...", "key": "docker/authelia:no-git"} ] } ``` ## Diffing two runs ```bash python scripts/audit.py --format json -o this-week.json # ... a week later ... python scripts/audit.py --format json -o next-week.json python3 - <<'PY' import json a = {f["key"] for f in json.load(open("this-week.json"))["findings"]} b = {f["key"] for f in json.load(open("next-week.json"))["findings"]} print("NEW drift :", sorted(b - a)) print("REMEDIATED :", sorted(a - b)) PY ```