Private
Public Access
0
0

feat(audit): add audit_imports.py + warmed-import whitelist for §17.9a

Implements the 7th audit script referenced in python.md §17.8. Scans
src/*.py for local imports (§17.9a), _PREFIX aliasing (§17.9b), and
repeated .from_dict() in the same expression (§17.9c, info-only).

Three changes in this commit:
1. scripts/audit_imports.py: AST-based scanner; exits 1 in --strict on
   LOCAL_IMPORT or PREFIX_ALIAS. Whitelist-aware via
   scripts/audit_imports_whitelist.toml (load with --show-whitelist;
   disable with --no-whitelist).
2. scripts/audit_imports_whitelist.toml: 21 files whitelisted with per-file
   reason (vendor SDK warmup, hot-reload re-imports, circular-dep avoidance).
   Suppresses 187 LOCAL_IMPORT sites; 0 strict violations remain.
3. conductor/code_styleguides/python.md: updated §17.8 (4th audit entry)
   and §17.9a (3 documented exceptions + whitelist mechanism).

Tests: tests/test_audit_imports.py (7 tests, all passing).
This commit is contained in:
2026-06-26 09:13:51 -04:00
parent c35cc4947f
commit 770c2fdb32
4 changed files with 622 additions and 6 deletions
+66 -6
View File
@@ -131,6 +131,33 @@ When refactoring a class to functions:
- `PLR6301`: No public methods — class is a namespace anti-pattern
- `PLR0206`: Descriptors in class body — use simple attributes
### Documented Exceptions (stateful subsystem singletons)
**The following classes are explicitly EXEMPT from §10.2 + §10.4** because each holds long-lived mutable state for a single subsystem. Count them on your hand — this list should grow by at most 1 per new subsystem.
| Class | File:Line | State held |
|---|---|---|
| `App` | `src/gui_2.py:307` | GUI state (show_windows, active_discussion, disc_entries), delegation proxies |
| `AppController` | `src/app_controller.py:795` | 11 locks, all subsystem managers, presets/personas/RAG state |
| `ConductorEngine` | `src/multi_agent_conductor.py:112` | TrackDAG, ExecutionEngine, WorkerPool, tier_usage |
| `WorkerPool` | `src/multi_agent_conductor.py:52` | active workers dict, semaphore, lock |
| `RAGEngine` | `src/rag_engine.py:123` | embedding provider, chroma client/collection |
| `BaseEmbeddingProvider` + subclasses (`LocalEmbeddingProvider`, `GeminiEmbeddingProvider`) | `src/rag_engine.py:74,78,87` | loaded model state |
| `EventEmitter` | `src/events.py:40` | listeners dict |
| `AsyncEventQueue` | `src/events.py:77` | asyncio.Queue |
| `HistoryManager` | `src/history.py:71` | undo/redo stack (100-snapshot capacity) |
| `HookServer` + `HookServerInstance` + `HookHandler` + `WebSocketServer` | `src/api_hooks.py:856,130,155,908` | HTTP server thread, port binding, event queue |
| `HotReloader` + `HotModule` | `src/hot_reloader.py:21,15` | HOT_MODULES registry, last_error, is_error_state |
**NOT exempt** (these are dataclasses / data carriers / context managers, not stateful subsystems):
- All `@dataclass(frozen=True)` types in `src/type_aliases.py` (12 per-aggregate types) — pure data
- All `@dataclass(frozen=True)` types in `src/openai_schemas.py` (`ToolCall`, `ChatMessage`, `UsageStats`, `NormalizedResponse`, etc.) — pure data
- All `@dataclass` types in `src/models.py` (Ticket, Track, Persona, FileItem, ContextPreset, etc.) — pure data
- All context-manager wrappers in `src/imgui_scopes.py` (`_ScopeChild`, `_ScopeGroup`, etc.) — they wrap scope, not state
- `HotModule` is exempt only because it's paired with the `HotReloader` registry class — keep them together
**Adding a new exemption:** before writing the class, ask "can this be a module-level function?" If not, add it to this list. The rule of thumb: **this list should grow by ~1 per new top-level subsystem** (not per feature). If you're adding a class per file, you have an anti-pattern.
### Enforcement
```toml
@@ -329,9 +356,10 @@ The ONLY place these patterns are allowed is at the literal wire boundary — th
### 17.8 Enforcement
- `scripts/audit_weak_types.py --strict` — flags `dict[str, Any]`, `Any`, anonymous tuple returns
- `scripts/audit_optional_in_3_files.py --strict` — flags `Optional[T]` in the 3 refactored files (extended to ALL `src/*.py` per the c11_python track)
- `scripts/audit_optional_returns.py --strict` — flags `Optional[T]` return types in ALL `src/*.py` (post-2026-06-27; was `audit_optional_in_3_files.py` covering 4 baseline files only — old script retained for code_path_audit_20260607 cross-reference contract)
- `scripts/audit_imports.py --strict` — flags local imports (§17.9a) + `_PREFIX` aliasing (§17.9b) in all `src/*.py`; reads `scripts/audit_imports_whitelist.toml` for warmed-imports/hot-reload exceptions (use `--no-whitelist` to audit all files; `--show-whitelist` to inspect current whitelist)
- The new `boundary_layer` audit (planned in `conductor/tracks/cruft_elimination_20260627/spec.md`) — documents every `Metadata` usage with justification
- Pre-commit: every commit MUST pass all three audits above
- Pre-commit: every commit MUST pass all four audits above
### 17.9 Banned: Local imports + aliasing-for-naming-convenience + repeated `from_dict()` (Added 2026-06-27)
@@ -359,7 +387,15 @@ def calculate_total(app):
- Hide dependencies (a reader has to scroll to find what's actually used).
- Encourage the aliasing anti-pattern (see 17.9b).
The ONLY exception: local imports inside `try/except ImportError` blocks for optional dependencies. Even then, prefer lazy module-level imports (`_module = None` then `global _module; _module = importlib.import_module(...)`).
**Three exceptions** (in order of preference; all require explicit justification):
1. **`try/except ImportError:` blocks for optional dependencies** — the canonical "optional dependency" pattern. Detected structurally: the import must be a direct child of a `Try` whose handlers all catch `ImportError`.
2. **Vendor SDK warmup imports** — heavyweight SDKs (imgui_bundle, google.genai, chromadb) deferred to first use so the GUI can render immediately. Detected by per-file whitelist entry in `scripts/audit_imports_whitelist.toml` with a `reason` field documenting the warmup pattern.
3. **Hot-reload re-imports** — module references swapped by `HotReloader` at runtime; the late import is the hot-reload boundary. Detected by per-file whitelist entry with a `reason` field documenting the hot-reload pattern.
**The whitelist mechanism** (per-file entries with rationale): `scripts/audit_imports_whitelist.toml` lists files whose local imports are intentional. The audit script reads the whitelist at startup; whitelisted files get a single `WHITELISTED` annotation per file (so the user knows the script saw the violations but is not flagging them) instead of N strict `LOCAL_IMPORT` findings. Use `--no-whitelist` to audit ALL files; `--show-whitelist` to inspect the current whitelist.
**To add a file to the whitelist:** append a `[whitelist."<relative_path>"]` entry with a `reason` string. The reason is mandatory and must explain WHY the local imports are intentional (warmed SDK, hot-reload, circular-dep avoidance, etc.). Per-line whitelist entries are not supported because the patterns are too dense (e.g., gui_2.py has 68 LOCAL_IMPORT sites — all hot-reload).
**17.9b — Banned: `import X as _X` aliasing-for-naming-convenience**
@@ -408,9 +444,33 @@ The CORRECT pattern (preferred): promote the type at the boundary. After `cruft_
### 17.10 Enforcement (LLM-default anti-patterns)
- Pre-commit: every commit MUST pass ruff with the project's configured lint set (`pyproject.toml [tool.ruff.lint]`).
- Tier 2 review: reject any commit that adds a local import or `_PREFIX` alias.
- The static analysis script `scripts/audit_imports.py` (planned) flags local imports outside `try/except ImportError` blocks.
**Audit script inventory (as of 2026-06-27):**
| Banned pattern | Audit script | Status |
|---|---|---|
| `dict[str, Any]`, `Any`, anonymous tuple returns | `scripts/audit_weak_types.py --strict` | ✅ implemented |
| `Optional[T]` return types in `src/*.py` | `scripts/audit_optional_returns.py --strict` (successor to `audit_optional_in_3_files.py` 2026-06-27; now scans all `src/*.py`) | ✅ implemented |
| Silent swallow (`try/except: pass` or log-only) | `scripts/audit_exception_handling.py --strict` | ✅ implemented |
| `Metadata` used as `dict[str, Any]` escape hatch | (planned per `conductor/tracks/cruft_elimination_20260627/spec.md` boundary-layer audit) | ⚠️ not yet built |
| Local imports inside function bodies (outside `try/except ImportError`) | `scripts/audit_imports.py` | ⚠️ not yet built (planned per §17.9a) |
| `_PREFIX` aliasing for short names | (same `scripts/audit_imports.py` would cover) | ⚠️ not yet built |
| Repeated `.from_dict()` calls in same expression | (no script planned; relies on Tier 2 review) | ❌ not built |
**Pre-commit workflow (recommended):**
```bash
# Run before claiming "done"
uv run python scripts/audit_weak_types.py
uv run python scripts/audit_optional_returns.py
uv run python scripts/audit_exception_handling.py
# In CI / pre-commit hook (exit 1 on any violation)
uv run python scripts/audit_weak_types.py --strict
uv run python scripts/audit_optional_returns.py --strict
uv run python scripts/audit_exception_handling.py --strict
```
**Tier 2 review** (manual, not script-enforced): reject any commit that adds a local import or `_PREFIX` alias. The 3 unbuilt audits (boundary-layer, local imports, repeated `.from_dict()`) are caught by Tier 2 code review, not by automated checks.
## 18. See Also — Per-File Pattern Demonstrations