audit-py-codebase
Perform a single critical audit pass on a Python codebase folder. Designed to be re-invoked until convergence (zero remaining issues).
Instructions
You are performing a single audit pass on the Python codebase at $ARGUMENTS. This skill is idempotent and convergent — each invocation examines the current state, fixes what it can, and reports what remains.
Phase 0: Baseline Snapshot
- Run
ruff check <folder> --statistics and capture violation count.
- Run
pyright <folder> --level strict and capture error count.
- Run
pytest --tb=line -q (if tests exist) and capture pass/fail count.
- Record these numbers — you will compare against them at the end.
Phase 1: Type Annotations
Ensure every variable, parameter, and return value is properly annotated.
Rules:
- Every function/method has fully annotated parameters and
-> ReturnType.
- Every assignment where the type is non-obvious has an annotation (e.g.,
results: list[dict[str, Any]] = []).
- Class attributes are annotated at class body level or in
__init__.
- Use modern syntax:
list[X] not List[X], X | None not Optional[X] (Python 3.11+).
- No untyped
Any without a # type: ignore[<rule>] comment explaining why.
- Run
pyright --level strict after changes — target zero errors.
Phase 2: Docstrings (PEP-257 + NumPy Style)
Module-level: Every .py file starts with a docstring describing its purpose, responsibilities, and key exports.
Functions/Methods (NumPy style):
def function_name(param: Type) -> ReturnType:
"""
Short summary (imperative mood, one line).
Extended description if the function is non-trivial.
Parameters
----------
param : Type
Description of the parameter.
Returns
-------
ReturnType
Description of what is returned.
Raises
------
SpecificError
When this error is raised.
"""
Classes (NumPy style):
class ClassName:
"""
Short summary.
Extended description.
Attributes
----------
attr_name : Type
Description.
Notes
-----
Any important implementation notes.
"""
Rules:
- Every public module, class, function, method gets a full docstring.
- Private helpers (
_func) get at minimum a one-line docstring.
__init__ documents parameters under the class docstring's Parameters section.
- Static methods and class methods follow the same NumPy format.
- Run
ruff check --select D to verify formatting compliance.
Phase 3: Code Quality (SOLID, DRY, Pure Functions)
Scan for and refactor:
| Principle |
Smell |
Action |
| Single Responsibility |
Function > 30 LOC or does 2+ things |
Extract focused functions |
| Open/Closed |
if/elif chains switching on type strings |
Use polymorphism or strategy |
| Liskov Substitution |
Subclass breaks parent's contract |
Fix or use composition |
| Interface Segregation |
ABC forces unused method implementations |
Split into focused Protocols |
| Dependency Inversion |
High-level imports concrete low-level |
Inject via constructor/Protocol |
| DRY |
Copy-pasted logic (3+ lines, 2+ locations) |
Extract utility |
| Pure Functions |
Function computes AND performs I/O |
Separate pure logic from effects |
Rules:
- Do NOT over-abstract. Only extract when duplication represents the same concept.
- Prefer composition over inheritance.
- Ensure refactored code passes existing tests before proceeding.
Phase 4: Error Handling
Rules:
- No bare
except: or broad except Exception: without re-raise.
- No swallowed exceptions (
except E: pass) — at minimum, log the error.
- External boundaries (APIs, file I/O, user input) MUST have try/except with specific types.
- Use
raise ... from e to preserve exception chains.
- Create domain-specific exceptions where generic ones are used repeatedly.
- Functions should raise on failure, not return sentinel values (None, -1, False).
Phase 5: Logging
Rules:
- Every module:
logger = logging.getLogger(__name__) — no print() for operational output.
- Appropriate levels:
DEBUG (internals), INFO (flow), WARNING (recoverable), ERROR (failures).
- Use lazy formatting:
logger.info("Processing %s", case_id) — not f-strings.
- Never log sensitive data (credentials, PHI, PII, tokens).
- Verify a central logging configuration exists (file, function, or config dict).
- Add at least one test using
caplog fixture to verify critical log messages fire.
Phase 6: Testing (pytest + Fixtures)
Rules:
- Run
pytest --cov=<folder> --cov-report=term-missing to identify untested code.
- Shared test state goes in
conftest.py as fixtures — no test-level setup boilerplate.
- Use
@pytest.fixture for reusable objects, @pytest.mark.parametrize for edge cases.
- Test pure logic thoroughly (unit), test boundaries with mocks (integration).
- Every custom exception should have a test that triggers it.
- Every error handling path (Phase 4) should have a test that exercises it.
- Async tests: use
pytest-asyncio with asyncio_mode = "auto".
Phase 7: Final Clean Code Gate
Run in sequence — all must pass with zero violations:
ruff format <folder> — apply formatting.
ruff check <folder> --fix — auto-fix what's possible.
ruff check <folder> — confirm zero remaining.
pyright <folder> --level strict — confirm zero errors.
- Review any
# type: ignore — each must have a [rule] code and justification.
Phase 8: Smoke Tests
pytest tests/ -v --tb=short — full suite must pass.
python -c "import <package>" — confirm no import errors.
- If an entry point exists (server, CLI), start it and confirm it boots without errors.
- Compare final ruff/pyright counts against Phase 0 baseline.
Output: Audit Scorecard
End every invocation with this exact format:
── Audit Scorecard ─────────────────────────────────────────
Pass: N (where N is which re-invocation this is, default 1)
Folder: <folder_path>
pyright errors: [before] → [after]
ruff violations: [before] → [after]
missing docstrings:[before] → [after]
test coverage: [before]% → [after]%
tests passing: [before] → [after]
Issues fixed this pass: X
Issues remaining: Y
Items needing human decision:
- [list each item with file:line and reason]
Recommendation: RE-RUN | CONVERGED ✓
────────────────────────────────────────────────────────────
Recommendation logic:
RE-RUN if any issues remain that another pass could fix.
CONVERGED ✓ if pyright=0, ruff=0, all tests pass, and no new issues found.
Important Constraints
- Commit changes at the end of each phase (not at the end of the full pass) with a message like
audit: phase N — <description>.
- If a refactoring might break things, run
pytest immediately after — do not accumulate risk.
- Do NOT add features, new functionality, or speculative abstractions.
- Do NOT delete tests or weaken assertions to make them pass.
- If unsure whether a change is safe, flag it in "Items needing human decision" and skip it.
1---2name: audit-py-codebase3description: Critical audit of a Python codebase — type annotations, docstrings, SOLID/DRY, error handling, logging, pytest, ruff, pyright. Re-invoke until converged.4---56# audit-py-codebase78Perform a single critical audit pass on a Python codebase folder. Designed to be re-invoked until convergence (zero remaining issues).910## Instructions1112You are performing a **single audit pass** on the Python codebase at `$ARGUMENTS`. This skill is idempotent and convergent — each invocation examines the current state, fixes what it can, and reports what remains.1314### Phase 0: Baseline Snapshot15161. Run `ruff check <folder> --statistics` and capture violation count.172. Run `pyright <folder> --level strict` and capture error count.183. Run `pytest --tb=line -q` (if tests exist) and capture pass/fail count.194. Record these numbers — you will compare against them at the end.2021### Phase 1: Type Annotations2223Ensure every variable, parameter, and return value is properly annotated.2425**Rules:**26- Every function/method has fully annotated parameters and `-> ReturnType`.27- Every assignment where the type is non-obvious has an annotation (e.g., `results: list[dict[str, Any]] = []`).28- Class attributes are annotated at class body level or in `__init__`.29- Use modern syntax: `list[X]` not `List[X]`, `X | None` not `Optional[X]` (Python 3.11+).30- No untyped `Any` without a `# type: ignore[<rule>]` comment explaining why.31- Run `pyright --level strict` after changes — target zero errors.3233### Phase 2: Docstrings (PEP-257 + NumPy Style)3435**Module-level:** Every `.py` file starts with a docstring describing its purpose, responsibilities, and key exports.3637**Functions/Methods (NumPy style):**38```python39def function_name(param: Type) -> ReturnType:40 """41 Short summary (imperative mood, one line).4243 Extended description if the function is non-trivial.4445 Parameters46 ----------47 param : Type48 Description of the parameter.4950 Returns51 -------52 ReturnType53 Description of what is returned.5455 Raises56 ------57 SpecificError58 When this error is raised.59 """60```6162**Classes (NumPy style):**63```python64class ClassName:65 """66 Short summary.6768 Extended description.6970 Attributes71 ----------72 attr_name : Type73 Description.7475 Notes76 -----77 Any important implementation notes.78 """79```8081**Rules:**82- Every public module, class, function, method gets a full docstring.83- Private helpers (`_func`) get at minimum a one-line docstring.84- `__init__` documents parameters under the class docstring's `Parameters` section.85- Static methods and class methods follow the same NumPy format.86- Run `ruff check --select D` to verify formatting compliance.8788### Phase 3: Code Quality (SOLID, DRY, Pure Functions)8990Scan for and refactor:9192| Principle | Smell | Action |93|-----------|-------|--------|94| Single Responsibility | Function > 30 LOC or does 2+ things | Extract focused functions |95| Open/Closed | `if/elif` chains switching on type strings | Use polymorphism or strategy |96| Liskov Substitution | Subclass breaks parent's contract | Fix or use composition |97| Interface Segregation | ABC forces unused method implementations | Split into focused Protocols |98| Dependency Inversion | High-level imports concrete low-level | Inject via constructor/Protocol |99| DRY | Copy-pasted logic (3+ lines, 2+ locations) | Extract utility |100| Pure Functions | Function computes AND performs I/O | Separate pure logic from effects |101102**Rules:**103- Do NOT over-abstract. Only extract when duplication represents the same concept.104- Prefer composition over inheritance.105- Ensure refactored code passes existing tests before proceeding.106107### Phase 4: Error Handling108109**Rules:**110- No bare `except:` or broad `except Exception:` without re-raise.111- No swallowed exceptions (`except E: pass`) — at minimum, log the error.112- External boundaries (APIs, file I/O, user input) MUST have try/except with specific types.113- Use `raise ... from e` to preserve exception chains.114- Create domain-specific exceptions where generic ones are used repeatedly.115- Functions should raise on failure, not return sentinel values (None, -1, False).116117### Phase 5: Logging118119**Rules:**120- Every module: `logger = logging.getLogger(__name__)` — no `print()` for operational output.121- Appropriate levels: `DEBUG` (internals), `INFO` (flow), `WARNING` (recoverable), `ERROR` (failures).122- Use lazy formatting: `logger.info("Processing %s", case_id)` — not f-strings.123- Never log sensitive data (credentials, PHI, PII, tokens).124- Verify a central logging configuration exists (file, function, or config dict).125- Add at least one test using `caplog` fixture to verify critical log messages fire.126127### Phase 6: Testing (pytest + Fixtures)128129**Rules:**130- Run `pytest --cov=<folder> --cov-report=term-missing` to identify untested code.131- Shared test state goes in `conftest.py` as fixtures — no test-level setup boilerplate.132- Use `@pytest.fixture` for reusable objects, `@pytest.mark.parametrize` for edge cases.133- Test pure logic thoroughly (unit), test boundaries with mocks (integration).134- Every custom exception should have a test that triggers it.135- Every error handling path (Phase 4) should have a test that exercises it.136- Async tests: use `pytest-asyncio` with `asyncio_mode = "auto"`.137138### Phase 7: Final Clean Code Gate139140Run in sequence — all must pass with zero violations:1411421. `ruff format <folder>` — apply formatting.1432. `ruff check <folder> --fix` — auto-fix what's possible.1443. `ruff check <folder>` — confirm zero remaining.1454. `pyright <folder> --level strict` — confirm zero errors.1465. Review any `# type: ignore` — each must have a `[rule]` code and justification.147148### Phase 8: Smoke Tests1491501. `pytest tests/ -v --tb=short` — full suite must pass.1512. `python -c "import <package>"` — confirm no import errors.1523. If an entry point exists (server, CLI), start it and confirm it boots without errors.1534. Compare final ruff/pyright counts against Phase 0 baseline.154155### Output: Audit Scorecard156157End every invocation with this exact format:158159```160── Audit Scorecard ─────────────────────────────────────────161 Pass: N (where N is which re-invocation this is, default 1)162 Folder: <folder_path>163 164 pyright errors: [before] → [after]165 ruff violations: [before] → [after]166 missing docstrings:[before] → [after]167 test coverage: [before]% → [after]%168 tests passing: [before] → [after]169 170 Issues fixed this pass: X171 Issues remaining: Y172 Items needing human decision:173 - [list each item with file:line and reason]174175 Recommendation: RE-RUN | CONVERGED ✓176────────────────────────────────────────────────────────────177```178179**Recommendation logic:**180- `RE-RUN` if any issues remain that another pass could fix.181- `CONVERGED ✓` if pyright=0, ruff=0, all tests pass, and no new issues found.182183### Important Constraints184185- Commit changes at the end of each phase (not at the end of the full pass) with a message like `audit: phase N — <description>`.186- If a refactoring might break things, run `pytest` immediately after — do not accumulate risk.187- Do NOT add features, new functionality, or speculative abstractions.188- Do NOT delete tests or weaken assertions to make them pass.189- If unsure whether a change is safe, flag it in "Items needing human decision" and skip it.