ADR 0014: Dead Code Analysis and Repository Hygiene
Ratification
Adopted before ADR 0018. There was no separate ratification process. Git history for this file on main is the record.
- Discussion Issue: not recorded (before ADR 0018)
- Merge PR: see git history for this file
- Accepted: as merged to
main
Context
Why this matters: Dead code is not harmless—it confuses readers, hides the real execution path, and makes refactors riskier. Unused imports are easy wins: the linter catches them cheaply. Whole unused functions are harder: static “dead code” detectors use heuristics and can false-positive on dynamic registration or tests-only symbols.
We combine a strict gate (Ruff on every PR) with a softer periodic scan (Vulture) so routine work stays fast while deeper cleanup still happens on a schedule.
Decision
Adopt a two-layer approach:
-
Ruff (required on every PR): keep the existing
E/Frules so F401 (unused imports) is enforced inmake lint-check/ CI. Extend with RUF100 to remove obsoletenoqadirectives that no longer match an enabled rule. -
Vulture (advisory): configure
[tool.vulture]inpyproject.tomlfor pathsapp,tests, andscripts. Run locally viamake dead-code-check. Do not add Vulture to the default PR quality gate; run it on a weekly schedule (and manual dispatch) in GitHub Actions so maintainers triage findings without blocking every change.
Removals of allegedly dead code require human confirmation and tests (or equivalent evidence) before merge; static analysis alone is not sufficient for deletion.
Scope
- In scope: configuration, Make target, scheduled workflow, contributor checklist.
- Out of scope: automatic deletion, mandatory Vulture on every PR, full codebase purge in one change.
Alternatives considered
-
Vulture in CI for every PR.
- Pros: fastest feedback.
- Cons: false positives create churn; tuning whitelists becomes a constant tax.
-
Ruff-only (no Vulture).
- Pros: zero extra dependency, very stable signal for imports.
- Cons: misses unused functions/classes that imports still reference indirectly.
Consequences
Positive
- Unused imports and stale
noqamarkers stay caught by the normal gate. - Optional deeper scans run on a predictable cadence without blocking routine work.
Trade-offs
- Maintainers must occasionally review scheduled workflow failures.
- Vulture configuration or
--ignore-namesmay need updates when patterns change.
Implementation notes
- Make:
dead-code-checkrunspython -m vulture(readspyproject.toml). - Workflow:
.github/workflows/dead-code.yml(weekly +workflow_dispatch). - Ruff:
tests/conftest.pyuses[tool.ruff.lint.per-file-ignores]for E402 (imports after environment setup); prefer that over per-linenoqaso RUF100 stays meaningful. - Contributor expectations: see the “Dead code and unused imports” section in repository root
CONTRIBUTING.md.
Page history
| Date | Change | Author |
|---|---|---|
| Added Page history section (repository baseline). | Ivan Boyarkin |