How we migrated 137 modules to a new dependency-injection pattern with AI, turning a quarter-long slog into a few focused weeks by teaching the AI instead of delegating to it, working in small supervised steps, and letting the method compound.
Every large codebase eventually accumulates a debt it can’t pay off in a single sprint. Ours had a good one: dependency injection wired the old way, threaded through 137 modules, every folder in the app that exposes a public index.ts, spread across features, services, components, and core. Migrating all of it to our new pattern was the kind of task that, done by hand, disappears into a quarter and takes a piece of the team with it.
So we did it with AI. And the interesting part isn’t that we used AI, everyone is. It’s how. Because the first way we tried, the obvious way, failed within a day. What actually worked was almost the opposite of the hype: less autonomy, not more. Smaller steps, not bigger leaps. Teaching before delegating.
This is the story of turning months into weeks without ever losing control of the wheel, and the mindset that got us there.
When you have 137 near-identical chores, the temptation is irresistible: spin up a fleet of agents, hand each one a module, and come back to a finished migration. Parallelism! Scale! We tried exactly that.
It broke immediately.
Here’s why, and it’s the single most important lesson in this whole piece: code that hasn’t been migrated yet has too many shades. From a distance, 137 modules look identical. Up close, every one has its own quirks, a slightly different way it registered dependencies, an odd circular import, a service that reached into another module’s internals, a test that mocked something three layers deep. No single “generic” transformation survives contact with that variety. An instruction general enough to cover every case is too vague to be safe; an instruction precise enough to be safe only covers a handful of cases.
And AI is flaky in a very specific, very dangerous way for this kind of work. It’s confidently wrong at the margins. It will migrate a module beautifully, then invent a subtle change in the one edge case you weren’t watching. In a normal feature, a small wrong change is a bug you catch in review. In a migration, a small wrong change is an avalanche. It compounds: the next module builds on the broken assumption, the shared mock drifts, the require-cycle you thought you fixed reappears three modules downstream. By the time a test finally goes red, you can’t tell which of the last twenty changes started the slide.
Unsupervised parallelism doesn’t just risk this, it guarantees it, and then hides the trigger. We shut it down after a day.
The reframe that saved the project was simple. Stop thinking of the AI as a workforce you dispatch. Start thinking of it as an apprentice you train.
So we did the first module ourselves. By hand. Slowly. Not because we couldn’t have muddled through with AI, but because we wanted a reference implementation, a gold-standard migration we fully understood, with every decision made deliberately. Then we turned to the AI and said, in effect: here is what good looks like; learn from it.
That reference became the seed for our tooling. Instead of prompts we retyped and hoped about, we captured the migration as reusable skills, small, named, single-purpose procedures, and specialized agents that knew how to apply them. One skill to migrate a single file. One to migrate a test alongside it. One to compose a module’s index.ts once its files were done. One to create the mock that keeps the test suite from crashing on native calls. Each did one thing, the way we’d done it by hand, and no more.
The AI wasn’t inventing the migration anymore. It was executing a method we’d authored, and that changed everything.
The second breakthrough was discovering that we could keep subdividing.
Our instinct had been “migrate a module” as the unit of work. But a module migration is really a chain: migrate each file, fix each file’s test, compose the index, wire the mocks, reconcile the imports. When we handed the AI the whole chain at once, we got a big diff and a binary outcome, it worked, or it failed somewhere in the middle and we went spelunking.
When we split it into those inner steps and ran them one at a time, two things happened. First, control, we inspected the seam after each step, so a mistake surfaced at step three instead of hiding until step nine. Second, speed of reaction, a wrong turn was a five-minute correction to one skill, not an archaeology dig through an hour of accumulated changes. Counterintuitively, doing it in more, smaller steps made us faster, because we spent almost no time debugging avalanches. There were no avalanches to debug.
This is the heart of it: granularity is how you tame flakiness. You don’t make AI reliable by trusting it more. You make it reliable by giving it less room to be wrong between checkpoints.
We had a method and we had tooling, but we didn’t yet have proof. So the second module wasn’t about speed, it was a controlled experiment. We ran the skills and agents end to end and watched: did they reproduce the quality of our hand-done reference? Did the steps compose cleanly? Where did the AI hesitate or guess?
They held up. And only then did we let ourselves scale.
That order matters. It’s tempting, once something works twice, to declare victory and unleash it. But the second module is where you’re still cheap to be wrong. Validating there, deliberately, skeptically, bought us the confidence to move fast everywhere after, precisely because we’d earned it instead of assumed it.
The last piece was sequencing. We didn’t migrate modules in whatever order they came up. We went from fewest dependencies to most, the leaf modules first, the deeply-connected ones last.
The reason is blast radius. A module nobody depends on can be migrated in near-isolation; if something’s off, it’s contained. Migrate that one first and it becomes solid ground for the next. Each completed module reduced the number of un-migrated dependencies the following modules had to worry about, so the hardest, most-connected modules, the ones we’d have dreaded on day one, were the easiest by the time we reached them, because almost everything they touched had already been migrated and proven.
The graph shrank as we climbed it. And through all of it, the app kept building, tests kept passing, and the rest of the team kept shipping features on top. Zero downtime. Nobody had to stop and wait for “the migration” to finish, because the migration was never a single disruptive event, it was a steady, safe advance.
Here’s the part that felt like magic, though it wasn’t. Every module we finished made the next one cheaper.
Not just because the dependency graph was shrinking, but because every quirk we hit sharpened the tooling. A module did something the skill didn’t expect? We taught the skill that shade, once, and every subsequent module inherited the lesson. The method didn’t stay static and degrade, it improved as it ran. By the back half of the migration, the skills had absorbed enough of the codebase’s variety that the AI handled cases in seconds that would have stumped it in week one.
That’s the curve that turns months into weeks. Not raw AI horsepower, a compounding method, supervised by people who kept feeding it back what they learned.
If you’re staring at a migration or a mechanical refactor at scale and wondering whether AI can eat it, it can. But not the way the demos imply. Here’s the practice, distilled:
The deepest shift is one of posture. It’s tempting to treat AI as an autopilot you switch on and walk away from. Treat it instead as an apprentice with real talent and no judgment, fast, tireless, occasionally and confidently wrong. Your job isn’t to do the work anymore. It’s to be the judgment: to teach, to check the seams, to sequence the approach, and to catch the small wrong thing before it becomes the big wrong thing.
Do that, and the quarter-long slog becomes a few focused weeks, with your architecture cleaner at the end than it’s ever been, and not a single day of downtime along the way.