vibecodiq"It worked fine for six months. Then every change became expensive." Your AI-generated app is live....
"It worked fine for six months. Then every change became expensive."
Your AI-generated app is live. Customers are paying. The dashboard looks fine. But every feature takes 2-3x longer than it should, and nobody can explain why.
This post explains what hidden technical debt looks like in AI-generated codebases, how to detect it, and the structural decomposition that fixes it.
Traditional technical debt is visible — you know you're cutting corners. AI-generated technical debt is invisible — the shortcuts happen at generation time and nobody notices.
Here's the mechanism:
Session 1: User service created (clean, 80 lines)
Session 15: Pricing logic added to user service ("it needs user data")
Session 30: Notification logic added to pricing flow ("notify on purchase")
Session 45: User service is now 600 lines, handles 4 domains
Session 60: "Why does adding a referral system take 3 weeks?"
Each session makes a locally reasonable decision. The AI puts code where the context is closest. Nobody checks whether the global architecture remains coherent.
DOMAIN SCATTERING MAP
userService.ts (620 lines)
- user logic ████████░░ 40%
- pricing logic ████░░░░░░ 20%
- notifications ███░░░░░░░ 15%
- audit logging ███░░░░░░░ 15%
- shared state ██░░░░░░░░ 10%
AFTER DECOMPOSITION:
users/userService.ts (120 lines)
pricing/priceCalculator.ts (90 lines)
notifications/notifier.ts (70 lines)
billing/auditLogger.ts (60 lines)
1 file x 5 domains --> 4 files x 1 domain each
find src -name "*.ts" -o -name "*.tsx" | xargs wc -l | sort -rn | head -20
What to look for:
Pick a core business concept (e.g., "pricing"). Count how many files contain it:
grep -rln "price\|pricing\|amount\|cost\|subscription\|billing" \
--include="*.ts" --include="*.tsx" src/ | wc -l
What the number means:
# Count module-level mutable variables
grep -rn "^let \|^var \|export let\|export var" \
--include="*.ts" --include="*.tsx" src/ | wc -l
Each one is a hidden coupling point. A change in one function silently affects every other function that reads or writes the same variable.
Look at your last 10 features or bug fixes. Calculate:
accuracy = (actual_time / estimated_time)
If the average is above 2.0, the architecture is fighting you. Hidden dependencies are making scope unpredictable.
Technical debt has interest rates. AI-generated codebases have higher interest rates because:
Shortcuts are invisible. Nobody chose to put pricing logic in the user service. The AI did it because that's where the context was.
Coupling compounds. Every feature added on top of a coupled module increases the coupling. By month 6, you can't change pricing without touching users, notifications, and payments.
The window closes. Refactoring in month 3 is a focused sprint. Refactoring in month 9 is a rewrite. The complexity grows faster than capacity.
Month 3: Refactoring cost = 2-3 days
Month 6: Refactoring cost = 2-3 weeks
Month 9: Refactoring cost = "we need to rewrite"
Month 12: "We're stuck"
Map every file to its primary business domain:
# Create a domain map
for file in $(find src -name "*.ts" -o -name "*.tsx"); do
echo "$file: $(head -5 $file | grep -o 'import.*from' | head -3)"
done
Identify files that serve multiple domains (these are the decomposition targets).
For each multi-domain file:
// BEFORE: src/services/userService.ts (600 lines, 4 domains)
export function getUser() { ... }
export function calculatePrice() { ... } // pricing domain
export function sendNotification() { ... } // notification domain
export function generateInvoice() { ... } // billing domain
// AFTER: 4 focused modules
// src/modules/users/userService.ts
// src/modules/pricing/priceCalculator.ts
// src/modules/notifications/notificationService.ts
// src/modules/billing/invoiceGenerator.ts
Replace module-level mutable variables with:
// BEFORE: shared mutable state
let currentUser: User | null = null;
export function setUser(user: User) { currentUser = user; }
export function getPrice() { return currentUser?.plan.price; } // hidden dependency
// AFTER: explicit data flow
export function getPrice(user: User): number {
return user.plan.price;
}
Add automated checks that prevent re-coupling:
// package.json script
{
"scripts": {
"check:boundaries": "npx madge --circular --extensions ts,tsx src/",
"check:size": "find src -name '*.ts' | xargs wc -l | awk '$1 > 500 {print \"WARN:\", $0}'",
"precommit": "npm run check:boundaries && npm run check:size"
}
}
Track these metrics weekly:
After structural decomposition, the cost of change becomes predictable. Adding a referral system touches the referral module — not pricing, users, notifications, and billing. Feature estimates become accurate because scope is bounded.
Hidden technical debt doesn't require a rewrite. It requires structural decomposition and ongoing enforcement.
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.