| Root scratch sprawl (seen) | out.txt err.txt *_out.txt tg_*.txt *.log tmp_* dummy.* sample*.txt at the repo root, sometimes 10s–100s of MB | AI pipes command output to CWD to "read" results and abandons it. Root-anchored .gitignore (/scratch, /*.log); delete; never let scratch reach root |
| Repro/debug dir graveyard (seen) | *_repro/, *_repro2..N/, .tmp_*/, debug_*/, scratch_*/ directories | Created while iterating, never cleaned. Delete + ignore |
| God-files that only grow (seen) | one module far past the size ceiling (5–10k+ LoC main/utils/handlers/repo_map) | AI appends to the file it already has open instead of splitting. Flag any module past the ceiling; split by responsibility |
| Duplicate "version" files | foo_v2, foo_new, foo_fixed, foo_final, foo.bak, foo copy.ts beside foo | AI copies-then-edits instead of replacing. Keep one; delete the rest (git is the history) |
| Convenience-typed boundaries (seen) | anyhow/any/interface{}/except Exception/# type: ignore in public signatures | Easiest type applied uniformly. Use typed errors/inputs at the public edge (invariant 5) |
| Tests that never run the real path | weak/tautological assertions (assert x is not None, toBeDefined, assertTrue(x)), tests that grep source, assert on constants, or mock the very thing under test | Green tests, broken runtime ("coverage lie"). Assert on actual values; require ≥1 test exercising the real path; add a smoke gate |
| No smoke gate (seen) | CI jumps straight to the full matrix; nothing checks "does it build + boot" first | Add the smoke test (build → boots → 1 happy path) as the first CI job |
.gitignore wrong in both directions (seen) | generated artifacts committed (coverage, logs, build/) AND the app lockfile ignored | AI adds files but doesn't maintain ignore rules. Ignore generated output; commit lockfiles for applications |
| Dead code left in place | large commented-out blocks, unused functions, # TODO: remove, _old_ shadows | AI is reluctant to delete. Remove it — git remembers |
| Layer-first by reflex (seen) | controllers/ services/ models/ for an app that is clearly feature-shaped | The most-represented training pattern, applied without fit-check. Re-evaluate vs feature-first (invariant 1) |
| Inline config/secrets/paths | hardcoded paths, URLs, model names, keys read deep inside modules | Convenience over boundary. Inject at the entrypoint (invariant 5) |
| Sibling experiment repos | proj, proj-rs, proj-v2, proj-ci-repro, proj-replay as separate top-level clones | Spun up for one experiment, never folded back or deleted. Consolidate or archive deliberately |
| Parallel implementations of one concept (research) | the same logic (auth, validation, HTTP client, date format, retry) implemented 3+ ways in different files | Context-window blindness: AI doesn't see the existing impl, so it writes a new one. The #1 AI smell. Grep for the concept before adding; consolidate to one; enforce with a banned-API lint |
| Abstraction bypass (research) | reaches for the raw library / inline call instead of the project's existing wrapper (DB query inside an HTTP handler; raw fetch instead of BaseClient) | The shared layer exists but wasn't in context. Route through the established layer; ban the raw API in lint |
| Porous public surface | package exports via import * / no __all__; everything pub or exported; internals freely imported across modules | No declared boundary → siblings reach into internals and coupling spreads. Declare the surface (__all__ + _private, pub(crate), internal/) and enforce with a boundary linter (invariant 3) |
| Modular mirage (research) | files ARE split, but related behavior is scattered across them with no cohesion — structural modularity ≠ logical modularity | "Can I point to the one place capability X lives?" If no, re-cohere (invariant 1) |
| Over-engineering / speculative generality (research) | factories/abstractions/config systems/defense-in-depth + handling impossible cases for a thing with ONE implementation; over-specification (80–90% of AI repos) | Delete the unused generality. YAGNI. Build for what the spec requires, not hypotheticals |
| Phantom validation (research) | static types treated as runtime validation; no schema check on external input at the boundary | Types evaporate at runtime. Validate inputs at the edge with a runtime schema (invariant 5) |
| Hallucinated / deprecated APIs (research) | calls to methods/flags that don't exist, or were removed in your installed version | Trained on old/averaged code. The build/typecheck + smoke gate catches these; verify against current docs |
| Error suppression ("afraid to fail") (research) | bare except/catch-and-continue, every failure → generic 500, errors logged-and-swallowed | AI optimizes for "runs" over "correct". Distinguish failure modes; propagate; don't swallow |