To refactor a legacy Kotlin module with Atlas, enumerate the callsites before you touch anything. Atlas maps the module's public surface with the lsp tool's documentSymbol operation, then runs findReferences on each exported symbol so no caller is a surprise. Behavior gets pinned first by running JUnit 5 via gradle test through the bash tool and recording the green baseline. Restructuring happens with apply_patch, which anchors each hunk on its context lines and fails with Failed to find context if the file has drifted, and the suite runs again after every hunk lands. Gradle drives the build, ktlint keeps the Kotlin style consistent, and a todowrite list tracks the callsites that remain.
How do you refactor a legacy Kotlin module without breaking its callers?
Atlas refactors a legacy Kotlin module in 2026 by enumerating callsites before editing, which is step 1 of its 5 step refactoring workflow. The lsp tool's documentSymbol operation maps the module's public surface, then findReferences runs on each exported symbol, so every call into that Kotlin package is on the list.
The risk in a Kotlin refactor is silent breakage at a callsite you did not know about, and Kotlin gives you several ways to hide one. An extension function declared in one file is called from another with no obvious import trail. A default argument means a caller compiles even after the signature grows. An interface implemented in a downstream Gradle subproject never shows up in the module you are editing. Atlas closes that gap with the lsp tool's findReferences before it touches anything, driving the Kotlin language server rather than a regex. Run atlas in a project with a build.gradle.kts, let Atlas read your modules, coroutines, and Gradle configuration, and the reference list is the actual scope of the refactor rather than your memory of it.
Why does Atlas run JUnit 5 via gradle test before changing Kotlin code?
Atlas runs JUnit 5 via gradle test through the bash tool before any Kotlin hunk lands, because a refactor is only provable against a green baseline. A legacy module with 200 passing tests and 3 pre-existing failures is a very different starting point from one that is fully green, and you need to know which you have.
Pinning behavior first is step two of Atlas's documented refactoring workflow, ahead of any restructuring. Run JUnit 5 via gradle test with the bash tool, record what passes, and record what already fails. Without that baseline, the first red test after a change is ambiguous: you cannot tell whether the refactor broke it or whether it was broken in main last Tuesday. In Kotlin this matters even more for coroutine-heavy modules, where a flaky test around runTest or a Dispatchers override can look exactly like a regression introduced by moving a suspend function. Gradle gives you the full test report, and Atlas's bash tool retains the complete log to a file when the output is long, so triage happens against the whole run.
What does apply_patch do in a Kotlin refactor?
apply_patch restructures Kotlin code by anchoring each hunk on its context lines and old_lines, and it refuses to apply against a drifted file. When a build.gradle.kts module has changed under you, apply_patch fails with Failed to find context rather than writing a hunk into the wrong place, which is step 3 of the 5 step workflow.
A legacy Kotlin module is usually refactored in a sequence of structural moves: extracting an interface, splitting a 900 line class into two files, converting a callback API to suspend functions. Each of those is a patch, and each patch has to land against the file it was written for. apply_patch seeks each hunk's context and old_lines, so if a rebase or another agent turn changed the surrounding Kotlin, the patch fails loudly instead of corrupting the file. Atlas computes a unified diff for every file edit and surfaces it for approval before writing, so you see the exact Kotlin lines going into src/main/kotlin/com/example/billing/Invoice.kt before they are written. Atlas snapshots file changes as git patches so edits can be diffed and rolled back if a structural move turns out to be wrong.
How often should Atlas re-run the Kotlin test suite during a refactor?
Atlas re-runs JUnit 5 via gradle test after each hunk lands, not once at the end. In a legacy Kotlin module a single apply_patch that extracts an interface can break 12 tests across 3 Gradle subprojects, and finding that out immediately is far cheaper than finding it out after six more patches.
Running the suite between hunks is step four of Atlas's refactoring workflow and it is the step people skip. The reason to keep it is attribution: when JUnit 5 via gradle test goes red directly after one apply_patch, the cause is that patch. When it goes red after seven, you are debugging a refactor instead of doing one. Atlas drives Gradle through the bash tool, so it can run a narrowed selection first and the full build afterwards. Run ktlint on the touched Kotlin files as you go, because a legacy module often has inconsistent formatting and letting ktlint reformat everything in one late commit buries the structural change in noise.
How do you track the remaining callsites in a Kotlin migration?
Atlas tracks the remaining Kotlin callsites in a todowrite list, one entry per callsite that findReferences turned up. A partially migrated module with 9 of 14 callsites moved is the most dangerous state a refactor can be in, and a todowrite list is what stops it from being mistaken for a finished one.
Legacy Kotlin refactors rarely finish in a single Atlas turn. A migration from callbacks to coroutines touches every caller, and each caller has its own compile errors and its own tests. Atlas fans out work to subagents that can run in the foreground or in parallel background sessions, so wide callsite sweeps can be delegated while the main session stays focused on the module itself. Keep every outstanding callsite in a todowrite list with a pending status, mark them off as JUnit 5 via gradle test goes green for each one, and the state of the migration is visible rather than remembered. Atlas reads git branches, status, and diffs, and can stage and create commits on your behalf, so a clean intermediate state can be committed the moment the build is green.
Step by step
- 01Run atlas in a project with a build.gradle.kts and let Atlas read your modules, coroutines, and Gradle configuration.
- 02Map the legacy module's public surface with the lsp tool's documentSymbol operation to list every exported Kotlin class, interface, and top-level function.
- 03Run the lsp tool's findReferences operation on each exported symbol to enumerate every callsite, including callers in other Gradle subprojects.
- 04Pin behavior first: run JUnit 5 via gradle test with the bash tool and record the green baseline, noting any pre-existing failures.
- 05Restructure with apply_patch, which seeks each hunk's context and old_lines and fails with Failed to find context if the Kotlin file has drifted.
- 06Approve the unified diff Atlas surfaces for each edit before it writes to src/main/kotlin.
- 07Re-run JUnit 5 via gradle test with the bash tool after each hunk lands, not once at the end.
- 08Run ktlint on the touched Kotlin files so the structural change is not buried in formatting noise.
- 09Track the remaining callsites in a todowrite list so a partially migrated module cannot be mistaken for a finished one.
- 10Review the whole diff and let Atlas stage and create the commit once Gradle builds clean.
Frequently asked questions
- How do I safely refactor a legacy Kotlin module with an AI agent?
- Enumerate the callsites first. Atlas runs the lsp tool's documentSymbol operation to map the module's public surface, then findReferences on each exported symbol, so the refactor's real scope is known before apply_patch touches a single Kotlin file.
- Can Atlas find every caller of a Kotlin function across Gradle subprojects?
- Yes. Atlas uses the lsp tool's findReferences operation, which asks the language server rather than grepping text, so callers in other Gradle subprojects and extension function usages are included in the reference list.
- What happens if a Kotlin file changes while Atlas is applying a patch?
- apply_patch seeks each hunk's context and old_lines and fails with Failed to find context if the file has drifted. The patch is rejected rather than applied to the wrong lines, and you re-read the file and regenerate the hunk.
- Does Atlas run gradle test during a Kotlin refactor?
- Atlas runs JUnit 5 via gradle test through the bash tool, first to record a green baseline before any change and then again after each hunk lands, so a red test can be attributed to the patch that caused it.
- How does Atlas handle converting Kotlin callbacks to coroutines?
- Run atlas in a project with a build.gradle.kts, ask it to convert callbacks to coroutines, and review the unified diff Atlas surfaces before it writes. findReferences enumerates the callers that must move with the signature, and a todowrite list tracks the ones still outstanding.
- Can I roll back a Kotlin refactor Atlas applied?
- Yes. Atlas snapshots file changes as git patches so edits can be diffed and rolled back. A structural move that turns out to be wrong is undone from the snapshot rather than reconstructed by hand.
- Does Atlas run ktlint on Kotlin files it edits?
- Atlas drives ktlint through its bash tool once the command is allowed by your permission rules. Running ktlint on only the touched files keeps a legacy module's structural diff readable instead of drowning it in reformatting.
Try Atlas in your terminal
The terminal-native AI coding agent. Free core, single binary.
Install AtlasRelated guides
Refactor a Legacy Module with Atlas in 2026
How to refactor a legacy module with Atlas in 2026: findReferences maps every callsite, apply_patch refuses to apply against a drifted file, and bash proves behavior.
Atlas for Kotlin in 2026
In 2026, Atlas empowers Kotlin developers with terminal-native AI coding. It integrates with Gradle and coroutines, offering secure, privacy-focused code assistance with local embeddings and granular control.
Rename a Symbol Across the Repo in Kotlin with Atlas (2026 Guide)
Rename a Kotlin class or function across every Gradle module with Atlas: lsp findReferences, grep, edit replaceAll, then JUnit 5 via gradle test and ktlint.
Migrate a Deprecated API Across Every Callsite in Kotlin with Atlas in 2026
Effortlessly migrate deprecated Kotlin APIs across your entire codebase in 2026 using Atlas. Leverage Gradle, JUnit 5, and ktlint for a verified, complete transition.
Trace a Runtime Bug From a Stack Trace in Kotlin with Atlas (2026)
Go from a Kotlin stack trace to the responsible line in 2026. Atlas reads each OrderService.kt:42 frame at its offset, greps the error string, and walks callers with lsp.
Atlas: Documenting Kotlin Modules with READMEs in 2026
Leverage Atlas in 2026 to generate accurate READMEs for your Kotlin modules. Atlas uses Gradle and JUnit 5 to document what your code actually does today, not what it was supposed to do a year ago.
Audit a Kotlin Repository with Parallel Atlas Subagents in 2026
Sweep your entire Kotlin repository for problems without blowing the main session's context window. Atlas uses parallel subagents and integrates with Gradle, JUnit 5, and ktlint for efficient, safe audits.
Add a Regression Test for a Kotlin Bug Fix with Atlas in 2026
Lock in Kotlin bug fixes with Atlas in 2026. Learn to write failing JUnit 5 tests via Gradle, apply fixes, and confirm success, all within your terminal.