A safe multi-file refactor: dependency map first, green build all the way through

A safe multi-file refactor is voiced in chunks that keep the build green. Draw the dependency map before the first edit, then dictate one chunk at a time. Run the full suite after every chunk and finish with a green merge..

Voiskey Voiskey Team
Green BuildsDependency MapChunk-After-Chunk

Why Big Refactors Go Red and Stay Red for a Week

Everyone has seen the refactor branch that went red on Tuesday and stayed red until the following Tuesday's call, and the cause is usually the same: the moves were batched. The module gets renamed, the helpers get split, and the two new namespaces arrive inside one giant commit, so when the suite fails nobody can say which of the eleven changes broke it, and the debugging starts at hour three with the branch already unshippable. The batching is not malice; it is the rhythm of a typing workflow, where a whole plan of moves gets executed while the context is fresh, and the red is discovered as one lump at the end. A safe multi-file refactor has to be organized differently, which is the entire subject of this article.

The red stays red for the second reason, which is household hygiene: once the branch is red, nobody wants to touch it, and nobody wants to be blamed for it, so the branch sits, and while it sits, main moves forward and merges get harder. The staying-red phenomenon is a social failure as much as a technical one, and the fix is a process that makes red difficult to stay in for more than an hour. A refactor where the suite is run after every spoken chunk never reaches the week of red, because the red is discovered at the size of one chunk, and a small red is a conversation while a big red is a crisis.

The third cause is the scope creep that arrives with the taste of restructuring: when you are already in the module, the dead imports feel cheap to remove, the duplicate function feels worth merging, and the naming feels worth normalizing. Each cheap addition is a new failure surface, and by the end the branch is three refactors wearing the name of one. Naming the plan out loud, with its edges, is the counter: this refactor moves the pricing model to its own module and nothing else; the duplicate function is a separate ticket. A spoken edge is the only boundary a refactor respects, since a quiet boundary is a boundary no one will remember.

The deeper point is that red is a signal, not a state: it tells you the delta you just introduced, and its value is proportional to how well that delta is isolated. A red after one explicit move tells you exactly what broke and makes the fix a five-minute task. A red after eleven moves tells you that something somewhere broke, and the investigation is a full afternoon. So the craft of a safe multi-file refactor is the craft of isolation: decide the moves, order the moves, and execute the moves one chunk at a time, with a green verification in between each, so the red never has a chance to become a week.

Drawing the Dependency Map Before the First Edit

Before the first edit, draw the dependency map, and draw it with your voice. Say the names of the files you are moving and the files that import them, one relationship per sentence: the pricing calculator is imported by the quote builder and by the invoice generator; the quote builder is imported by the dashboard; the invoice generator is imported by nothing outside the billing package. The assistant writes the map as you speak it, and the spoken map is better than a grep, because it forces you to name every relationship rather than accepting the first match that a glob returns. The map is the ten minutes that save the week.

Mark the map with the direction of change: white is untouched, yellow is the file you move, and green is the file that only needs a new import. The coloring is the discipline, because it exposes the silent assumption that a refactor is about the moved file when it is really about the importing files, which always outnumber the module being moved. If the map shows the dashboard importing six files that are all on the move, the refactor is a fan-out, and the fan-out is where the red lives. Speak the shape to the assistant: this is a fan-out, so the safe order is to introduce the new module first, then repoint the imports one by one.

Ask the assistant to verify the map against the actual codebase rather than trusting your recollection, then read the corrections aloud and update the map. The corrections are the value, since a refactor that begins on a wrong map is a refactor that produces surprises mid-week, and the surprise always arrives as a red that nobody planned. A map that has been verified is also the review artifact: put it in the branch description, and the reviewer reads the shape of the plan before the plan's consequences. The reviewer's first question, why are these seven files in this change, is answered by a picture that took you ten minutes to draw.

Finish the map by copying the import graph and the module's public interface in a short note: three sentences, the exported names and their callers. The note is the contract of the refactor, since a refactor of a module is only as complicated as its interface. If the interface is small, the refactor is mechanical and truly green-able; if the interface is eleven functions with six callers each, the refactor is really a redesign and should be scheduled as such, not merged into a Tuesday. Saying that truth out loud at the map stage is the cheapest honesty in software, and it is honestly the step most refactors skip.

Dictating the Moves in Chunks That Keep the Build Green

The map becomes a sequence of chunks, and each chunk is one spoken sentence with one behavior: create the new pricing module with the calculator copied unchanged. Say it, let the assistant perform it, then verify the build is green and the old file still proves its ownership. The chunk pattern is the entire secret, and its grammar is invariant: one move per chunk, no cleanup inside the chunk, and the build green at the end of the chunk. A move with cleanups inside is two changes wearing one name, and the build can no longer attribute its state, which is the exact condition this whole method exists to prevent.

Order the chunks by dependency, not by excitement: the new module and the new namespaces first, the repointing of the importers second, the deletion of the old file last. The intermediate state may be odd, the new module and the old one both exists, the importers of the old file still pointing at it, and that is the point, since odd but green is a state you can review, while tidy but red is a state you can only repair. Say the intermediate state out loud so it is intentional: we now have both modules, and nothing has changed behavior, and that is the checkpoint. Between checkpoints the branch is always shippable, which is the real requirement of a refactor in a team that likes to ship.

When the move involves a rename, dictate the rule that keeps the chunk small: rename the file and the exports, update the direct importers, and do not rename any variable inside the moved code in the same chunk, since the variable rename is a separate move that belongs to a chunk of its own. The two things, moving a file and restyling it, are different operations with different error surfaces, and the refactor that pays both debts at once is the refactor that cannot say which one broke. A chunk is safe when its failure would be obvious, and the most obvious failure is a missing import, not a quiet behavioral drift.

Keep the chunk list visible and cross off each chunk out loud as it lands. The ceremony has real function: it makes progress observable, it makes the branch reviewable one line at a time, and it produces the story that the merge request will tell. When a chunk goes red, the fix belongs to that chunk only, and the fix is usually a one-line import that the assistant spots in a second. If a chunk goes red twice in a row, stop the sequence and question the map instead of powering through, since a map that fails twice is a map that is lying, and the truth costs ten minutes to redraw rather than a day to excavate.

Running the Full Suite After Every Chunk Lands

The chunk runs the targeted tests to stay fast, and the full suite is the honest gate, so run both: the targeted suite during the chunk, the full suite after the chunk lands. The sequence sounds inefficient, and it is, deliberately, because the inefficiency is what the red-staying-red pattern trades away. A local targeted run takes a minute, the full suite takes ten, and ten minutes after each of ten chunks is a hundred minutes spent inside a day that previously lost a week. The spoken rhythm keeps the numbers honest: chunk three landed, the targeted tests are green, the full suite is running, and I am watching the persistence tests specifically because they touch the calculator's callers.

The full suite catches the cross-module breaks that targeted runs cannot see, which is the entire reason it exists for a refactor. The dashboard's rendering test, the invoice generator's golden file, the integration test that builds the whole app, these are the three places the pricing move will break, and breaking them is not a refactor failure but a refactor doing its job. When the full suite turns red at the dashboard test, the fix is a rename in the import or a changed path, and it is fixable in the minute because the chunk is one move. Say the attribution aloud so the team hears it: the red is at the dashboard, which is the repoint I made in this chunk, so the fix is exactly one line.

Leverage the assistant to read the failures while you read the diff, since the two reads split the work: the assistant reports the failing expectation, and you speak whether the new behavior is the intended one. The split is why voice works so well in the refactor loop: the assistant can scan forty failures and group them by cause in one sentence, and you then decide which groups are the refactor and which are the accidental behavior change. A refactor that accidentally changes behavior is the only red that actually matters, and it is far easier to spot in a grouped report than in a bullet list of red tests.

Keep a spoken tally of the full-suite runs as the day goes: green after chunk three, red then green after chunk four, never got past chunk six before lunch. The tally is the health signal of the plan. If the ratio is skewed, as in most chunks causing a red, then the map was too coarse or the chunks too large, and the honest adjustment is to redraw rather than to push on. The tally is also the answer to the question the reviewer will ask about the branch: how do I know the refactor did not change behavior, and the answer is the recorded sequence of green runs that the branch history now contains.

The Last Sweep: Dead Imports, New Names, One Green Merge

With the final chunk landed and the full suite green, the sweep has three passes, and each pass gets its own spoken command. The first pass is the dead imports: the moved module's old file is gone, the importers point at the new module, and somewhere in the branch there is a trail of obsolete imports, re-exports, and paths that have no readers left. Ask the assistant for the list, and read it against the map, because a dead import removal that misses a live one is a build error, while a live import marked dead is a behavior surprise, and the sweep is a conversation with the map, not a blind cleanup.

The second pass is the new names: exports, types, module paths, and their spellings consistent across the branch. Say the new vocabulary out loud once, the pricing calculator, the quote builder, the invoice generator, and let the assistant scan for stragglers still using the old name. The scan catches the quiet half-renames that the tests do not, since tests run on implementations and not on vocabulary, and the module with half-renamed exports becomes the trivia question of the next code review. A refactor that ends with an agreed vocabulary is worth the extra five minutes, and the agreed vocabulary is one sentence that everyone in the future will be grateful for.

The third pass is the branch hygiene: one clean diff per chunk rather than eleven files in a single commit, the dependency map attached to the description, and the sequence of green runs noted for the reviewer. Ask the assistant to squash nothing and rebase nothing; the history of the chunk-by-chunk progression is the review material and the archaeology. The reviewer who sees the map and the sequence reviews in twenty minutes instead of an hour, and the merge that lands quickly is the merge that keeps the team's trust in the refactor approach, which is the real asset this method builds.

The final act is the spoken verdict: the branch is green from the first commit to the last, the full suite ran after every chunk, behavior is identical, and this is ready to merge. Then merge, because the branch that stays open the night after the sweep is the branch that gets overgrown by morning's main. Review the refactor one week later, in a five-minute voice note: did the new module actually pay off, and does the dashboard feel calmer inside? The note answers the only question a refactor deserves, whether the code is easier for the person reading it next, and a positive answer is the green merge that the team will remember when the next refactor arrives.

FAQ s

A chunk is one move with one behavior: create the new module, repoint the importers, delete the old file, each as its own chunk. The test is the failure verifiability, so the chunk fails obviously, usually as a missing import, and the full suite runs green after it lands. Cleanups and renames get their own chunks.

Keep the branch shippable at every checkpoint by ordering chunks dependency-first: new modules and namespaces first, import repoints second, deletions last. The intermediate state is odd but green, and the full suite runs after every chunk. A branch that is green at every checkpoint never accumulates a week of red.

Run targeted tests during the chunk for speed and the full suite after the chunk for truth. The full suite is what catches cross-module breaks in the importing files, which is where refactors actually break. The ten minutes are cheap compared with the week of red that the skipped run produces.

Then it is two changes and it should say so: split the refactor branch and a behavior-change ticket, or split the branch into two commits, one preserving behavior and one changing it deliberately. A refactor that surprises a reviewer with behavior change is a trust problem, while a refactor that announces the change is a design discussion.

Say less. Get more
Say less. Get more
Say less. Get more
Say less. Get more
VoiskeyVoiskey

Your voice, amplified.

Draw the map aloud, move one behavior at a time, and run the suite after each step.

Use it across macOS, Windows, iOS, and Android

Use it across