The Lizard’s Tail

In biology, autotomy is when an animal sheds a body part that’s no longer useful. A lizard drops its tail to escape a predator. The tail was useful once, but survival requires letting it go. A new one grows back.

This is how you should think about refactoring a vibe-coded React Native app.

In early AI-coded MVPs, the first auth flow usually ships faster than anyone expects. The trouble starts a few weeks later, when the product changes and the team tries to carefully refactor code they never truly designed. That’s when login breaks, user state drifts, and the bug count goes up. They are trying to preserve code that should have been replaced.

Refactoring AI-generated code is expensive because it requires understanding intent you never had. Claude Code created abstractions you didn’t design. It made coupling decisions you didn’t approve. Refactoring that code means reverse-engineering architectural decisions made probabilistically, not intentionally.

Replace, Do Not Refactor

The alternative: if a module satisfies the same interface and passes the same tests, you can swap it without understanding the old implementation. The old module gets deleted. The new one takes its place. The composition root is the only file that knows.

// Module A (Auth v1) -> Interface Contract -> Module A' (Auth v2)

// The old auth module. You don't need to understand it.
// Claude Code generated it. It worked. Now it's done.
class SupabaseAuthService implements AuthService {
  // ... implementation you inherited ...
}

// The new auth module. You designed it. Claude Code helps write it.
class CustomAuthService implements AuthService {
  // ... your clean implementation ...
}

// The composition root is the ONLY place that changes:
const auth: AuthService = useSupabase
  ? new SupabaseAuthService()
  : new CustomAuthService()

The login screen doesn’t change. The route guards don’t change. The hooks don’t change. Only the composition root knows a swap happened. This is the power of interface-driven architecture when you’re working with AI-generated code. The interfaces are your insurance policy against technical debt.

Four Prerequisites for Safe Deletion

This only works with four things in place. Without them, you’re just deleting code and hoping. With them, you’re performing surgery:

  • Defined interfaces — Every module exposes a contract. TypeScript types, API schemas, clear boundaries. Cursor generates against these. Claude Code reads them. You own them.
  • Boundary enforcement — Modules can’t reach into each other’s internals. dependency-cruiser, ESLint, or convention prevents it.
  • Contract tests — Tests verify the interface, not the implementation. If the new module passes the same tests, it’s behaviorally equivalent.
  • Clean dependency graphs — No circular dependencies. No implicit coupling through globals. The import graph is a DAG, not a spiderweb.

Swapping Analytics Without Touching a Screen

You started with Segment because Claude Code suggested it. Now you need PostHog. The old module gets deleted. The new one implements the same interface. Every call site stays identical:

// Before
const analytics = new SegmentAnalytics(writeKey)

// After — same interface, different implementation
const analytics = new PostHogAnalytics(apiKey)

// Every call site stays the same.
// Cursor-generated screens don't change.
analytics.track('purchase_completed', { productId: '123' })
analytics.identify(userId, { plan: 'premium' })

The preservation instinct is strong. We want to carefully carry our code forward, preserving every behavior. But when you’re working with AI-generated code, the safest path is often to let go. Write good interfaces. Enforce boundaries. Then be willing to delete.

The Autotomy Expo Starter Pack is built around this philosophy — every module is designed for replacement, not preservation. When your vibe-coded app hits the scaling wall, you don’t untangle Claude Code’s architectural decisions. You replace them.