Custodian is a pip-installable cross-repo audit and maintenance toolkit for Python repos. It centralizes reusable detector logic and maintenance helpers so teams can stop re-implementing the same operational checks in each consumer repository.
- Detector framework with namespaced IDs (C / D / F / U / K / S / A / H / T / G / N / P / R)
- Adapters that wrap external tools (ruff, vulture, ty, semgrep, coverage)
- Plugin loader so consumer repos can add repo-specific detectors via
_custodian/ - CLI:
custodian-doctor(config validation),custodian-audit(run audits),custodian-triage(synthesize verdicts) - Schema-stable JSON output (
AuditResult.schema_version)
- A linter, formatter, or type checker — Custodian wraps those, it does not replace them
- A test runner
- A repo-specific tool — Custodian ships generic detectors; per-repo invariants live in consumer
_custodian/overlays - A CI provider — Custodian runs anywhere Python runs; CI integration is up to the consumer
Large multi-repo organizations need consistent health checks and maintenance routines, but each repo has local conventions. Custodian provides shared infrastructure (detector execution, schema-stable result output, plugin loading) while letting each consumer supply repo-specific plugins and config.
pip install custodian
custodian-doctor
custodian-auditFor local development against this repo:
pip install -e .[dev]Custodian runs three layers, in order, against any consumer repo:
- Native detectors — code-health classes (C/D/F/K/N/U/T), structural classes (A/H/R/S), boundary (B), and doc conventions (DC). Listed in the Detector model table below.
- Adapter pass — wraps Ruff, Vulture, ty, mypy, Semgrep, and Coverage. Toggled per-consumer via the
tools:block in.custodian/config.yaml. - Plugin detectors — repo-specific rules a consumer drops under
_custodian/and registers via theplugins:anddetectors:keys.
The runner emits a single AuditResult carrying every finding from every layer, keyed by detector ID. CLIs (custodian audit, custodian-doctor, custodian-triage) consume that one shape.
Detectors are grouped by namespace. Each detector has an ID, a severity (LOW/MEDIUM/HIGH), and a set of analysis passes it requires (none, ast_forest, call_graph).
| Class | Active | Focus |
|---|---|---|
| C | 16 | File-local code health: style, safety, security patterns |
| D | 4 | Dead code: unreachable paths, unused definitions, no-op constructs |
| F | 1 | Dead fields: unused dataclass / Pydantic fields and constants |
| U | 4 | Unimplemented stubs: raise NIE / ellipsis / docstring-only bodies |
| K | 3 | Documentation consistency: phantom symbols, value drift, param drift |
| S | 4 | Structure: layer violations, circular imports, test-in-prod imports, conftest guard |
| A | 2 | Architecture invariants: field counts, directory shape (declarative YAML) |
| H | 1 | Hexagonal layer ordering violations |
| T | 8 | Test shape: coverage, assertions, unconditional skips, parametrize hygiene, etc. |
| G | 1 | Ghost work: comment references to removed types |
| N | 1 | Naming: exception class naming convention |
| P | 1 | Partial implementations: hollow return bodies |
| R | 6 | README structure: presence, H1 match, "What X is / is not" sections, intro paragraph, docs/ index |
| B | 1 | Boundary: private-repo-name leakage in tracked files (configurable via privacy: block) |
| DC | 8 | Doc conventions: design front matter, dead doc refs, ADR naming, README sections, bare-symbol citations, docs/ taxonomy, orphan docs, README section ordering (configurable via doc_conventions: block) |
| M | 5 | Repo-meta: CHANGELOG/CONTRIBUTING/SECURITY/LICENSE presence + CHANGELOG Keep-a-Changelog format (configurable via repo_meta.skip: list) |
A further 21 detectors are kept as deprecated — third-party tools (ruff, vulture, ty) cover the same cases natively, so these no longer participate in audits but stay importable for backward compatibility. See docs/design/detector_disposition_matrix.md.
Consumer repos can add plugin detectors by supplying Python modules via the plugins key in .custodian/config.yaml. See tests/fixtures/sample_consumer/ for a concrete example.
Each consumer repo declares:
repo_key: stable identifiersrc_rootandtests_root: relative paths for scanningauditsettings (for example exclude_paths, stale handler names, and common words)plugins: module import targets used by Custodiantools: external-tool adapter toggles (ruff, vulture, ty, semgrep, coverage)maintenancethresholds
The preferred layout is .custodian/config.yaml at repo root (used by every ProtocolWarden platform repo). A legacy single-file .custodian.yaml form is still loaded as a fallback for backwards compatibility — see tests/fixtures/sample_consumer/.custodian.yaml for a minimal example of that form.
Custodian follows semantic versioning from day 1. Audit output is explicitly versioned using schema_version in AuditResult; v0.1 emits schema_version = 1.
GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later) — see LICENSE.