"Every time we add something, something else breaks."
If you've built an application with AI tools — Cursor, Lovable, Bolt.new, Replit, v0 — and you're past the first few months of development, there's a good chance you've heard this sentence. Maybe you've said it yourself.
This post breaks down the structural mechanism behind this pattern, how to detect it in your own codebase, and what the fix actually looks like.
The Pattern
Here's what we typically see in AI-generated codebases past month 3:
- Files that have grown past 500–800 lines
- Circular dependency chains between modules
- Business logic in unexpected places (route handlers, UI components, utility files)
- Less than 10% test coverage
- Every "simple" change requires extensive manual testing
This isn't bad code. It's structurally incoherent code.
The Mechanism: Architecture Drift
AI-assisted development optimizes locally. Each prompt solves the immediate problem without awareness of the broader architecture.
Session 1 establishes a clean boundary between your API routes and your business logic. Session 47 erodes it — because the fastest way to fix a bug is to put the fix where the symptoms are, not where the root cause lives.
Session 1: Clean boundary between modules
Session 15: First shortcut across boundary
Session 30: Business logic in route handlers
Session 47: Circular dependency chain forms
Session 60: "Every change breaks something else"
By month 6, a change in module A breaks module B because they share internal state that nobody intended to share. The dependency graph has become a web rather than a tree:
LOCAL OPTIMIZATION STRUCTURAL ENFORCEMENT
A <-> B A
| X | |
C <-> D B --> C
| X | |
E <-> F D --> E
circular chains: 4 circular chains: 0
blast radius: unknown blast radius: bounded
change cost: unpredictable change cost: predictable
Detection: Two Commands
1. Circular Dependencies
npx madge --circular --extensions ts,tsx src/
What the number means:
- 0 chains: Clean. Your modules can change independently.
- 1–2 chains: Warning. Coupling is forming.
- 3+ chains: Critical. Isolation is structurally impossible.
2. Oversized Files
find src -name "*.ts" -o -name "*.tsx" | xargs wc -l | awk '$1 > 500' | sort -rn
In AI-generated codebases, files over 500 lines usually contain logic from multiple domains. They are where "unrelated" breakage originates.
3. Test Coverage
npx jest --coverage --coverageReporters=text-summary 2>/dev/null | grep "Statements"
Less than 10% means the feedback loop between code changes and correctness verification is absent.
Why "Just Add Tests" Doesn't Fix This
Tests detect that a regression happened. They do not prevent the architectural condition that causes it.
If your architecture allows a pricing change to break authentication, tests will catch the failure — after the fact. But no amount of test coverage prevents the coupling itself. The change still leaks across boundaries because the boundaries don't exist.
The Structural Fix
The fix is not a rewrite. It's boundary enforcement — applied in sequence:
Step 1: Map the Current State
Run the detection commands above. Document:
- How many circular dependency chains exist
- Which files exceed 500 lines
- Where business logic actually lives vs. where it should live
Step 2: Establish Boundaries
Define which module owns which logic. The rule is simple:
One module = one domain = one direction of dependency
A pricing module can depend on a user module. A user module cannot depend on a pricing module. If they both need shared data, extract it into a shared types module that both can depend on.
Step 3: Break Circular Chains
For each circular dependency:
- Identify the shared state or logic that creates the cycle
- Extract it into a separate module
- Update both modules to depend on the extracted module instead of each other
Step 4: Decompose Oversized Files
Files over 500 lines get split by domain responsibility:
- Route handlers contain only routing logic
- Business logic lives in domain modules
- Shared utilities contain only pure functions
Step 5: Add Test Baseline
Once boundaries exist, tests become meaningful. Each test covers a bounded module, not the entire system. Focus on:
- Critical business paths (payments, auth, core workflows)
- Boundary contracts (what each module exports)
- Regression triggers (the specific changes that caused past breakage)
Step 6: Enforce Automatically
Set up CI/CD gates that block:
- New circular dependencies
- Files exceeding size thresholds
- Merges without test coverage on critical paths
This turns the architecture from "trust-based" to "enforcement-based." Changes that violate boundaries are caught before they reach production.
The Result
After structural stabilization, the blast radius of every change becomes predictable. A change in the pricing module affects pricing. A change in authentication affects authentication. "Every time we add something, something else breaks" disappears — not because the team is more careful, but because the architecture makes cross-module leakage structurally impossible.
This is part of the AI Chaos series — a structural analysis of failure patterns in AI-generated codebases. Based on ASA (Atomic Slice Architecture) — an open architecture standard for AI-generated software.
Resources
- ASA Standard — the open specification
- GitHub — source, examples, documentation
- Vibecodiq — structural diagnostics for AI-built apps
Top comments (0)