Atlas refactors a legacy Swift module by closing the gap where refactors actually fail: silent breakage at a callsite you did not know about. Atlas maps the module's public surface with the lsp tool's documentSymbol operation, runs findReferences on each exported symbol to enumerate every callsite in the Swift Package Manager graph, pins behavior by running XCTest via swift test and recording the green baseline, then restructures with apply_patch, which anchors on context lines and refuses to apply against a drifted file. Tests re-run after each hunk lands, not once at the end, and swift-format keeps the restructured Sources/ files canonical.
How do you map the public surface of a legacy Swift module?
Atlas maps a Swift module's public surface with the lsp tool's documentSymbol operation, which lists every type, protocol, and function a file declares. Run it across Sources/LegacyKit/, then run findReferences on each exported symbol to enumerate every callsite, all before changing 1 line of code.
Swift's access control gives you a real boundary to work with. A symbol marked `public` or `open` in a Swift Package Manager target can be called from any module that imports it, while `internal` symbols stop at the target. documentSymbol tells you which is which, and findReferences turns each public symbol into a concrete list of callers across the package's other targets and its Tests/ directory. The distinction changes the refactor's blast radius: reshaping an `internal struct` inside Sources/LegacyKit/ is contained, while changing a `public protocol` in the same target ripples into every dependent target declared in Package.swift. Atlas indexes code by AST declarations using tree-sitter, not blind line windows, so reading the module gives you whole `extension` blocks and whole function bodies rather than truncated fragments.
How do you pin Swift behavior before restructuring a module?
Pin behavior first: run the existing tests with Atlas's bash tool and record 1 green baseline before changing anything. In a Swift package that means `swift test`, which runs the XCTest cases under Tests/LegacyKitTests/. A refactor without a recorded baseline cannot distinguish a bug you introduced from one that was already there.
A legacy Swift module usually has partial test coverage, and the gaps are exactly where the refactor will hurt. Run `swift test` and write down what passed. If a public function in Sources/LegacyKit/ has no XCTest case at all, add one before restructuring, not after: an XCTAssertEqual against the current behavior is a characterization test, and it is the only thing standing between a refactor and a silent behavior change. Atlas computes a unified diff for every file edit and surfaces it for approval before writing, so the new test file in Tests/LegacyKitTests/ is reviewed like any other change. Track the untested symbols in a todowrite list so a module that is only half characterized is not mistaken for one that is safe to reshape.
How does apply_patch restructure Swift code without misapplying?
Atlas restructures Swift with apply_patch, which seeks each hunk's context and old_lines and fails with Failed to find context if the file has drifted. Splitting a 900-line Sources/LegacyKit/Session.swift into an extension per protocol conformance is a sequence of anchored patches, each one refusing to write against a stale file.
Swift refactors are usually a chain of dependent edits: extract a protocol, conform the existing type to it, move the implementation into an extension, update the initializer. By hunk five, the file no longer looks like it did when hunk one was written. apply_patch handles that honestly. Rather than guessing an offset, it looks for the context lines it expects and throws Failed to find context when they are gone, which turns a possible silent corruption into a stop-and-look. Atlas computes a unified diff for every edit and surfaces it for approval, so you see the `extension Session: Codable` block before it lands. Re-run the tests with bash after each hunk lands, not once at the end, so `swift test` failures point at the single hunk that caused them. Atlas snapshots file changes as git patches, so any hunk can be diffed and rolled back.
How do you confirm no Swift caller broke during a refactor?
Confirm no Swift caller broke by re-running the lsp tool's findReferences after each structural change, then letting the compiler check the rest with 2 commands. `swift build` fails on any caller whose signature no longer matches, and `swift test` proves the XCTest baseline you recorded before the refactor still holds.
Swift's compiler is a strong ally here, but only for callers inside the package. A `public` symbol consumed by a downstream package declared as a dependency in someone else's Package.swift will not be checked by your build, which is why findReferences enumeration happens first rather than being replaced by a compile. Keep a todowrite entry per remaining callsite so a partially migrated module cannot be mistaken for a finished one. Run `swift-format` over the touched files in Sources/ so the restructured code matches the package's style before review. Atlas reads git branches, status, and diffs, and can stage and create commits on your behalf, so the refactor can be committed as a series of small, behavior-preserving commits, each one green under `swift test`.
How does Atlas keep a Swift refactor reviewable and reversible?
Atlas keeps a Swift refactor reviewable through 3 guarantees: every Atlas tool call is permission-gated against allow, ask, and deny rules before it runs, Atlas computes a unified diff for every file edit and surfaces it for approval before writing, and Atlas snapshots file changes as git patches so edits can be rolled back.
Reshaping a legacy module means many edits to files you did not write, which is where an unreviewed agent does the most damage. Atlas puts a diff in front of you for every change to Sources/LegacyKit/, and the permission layer lets you allow bash for `swift test` while keeping edit on ask. If you want the enumeration and design done with no write capability at all, Atlas drafts a plan in a read-only plan agent and asks before switching to a build agent, so the callsite inventory and the extraction plan for your Swift Package Manager target exist before any file changes. For a private Swift package, Atlas can build its code index with local Ollama embeddings, keeping code off third-party servers, which lets codebase_search work while the source stays local.
Step by step
- 01Run atlas in the Swift package root, the directory containing Package.swift, so Swift Package Manager targets under Sources/ and Tests/ are in scope.
- 02Map the module's public surface with the lsp tool's documentSymbol operation across Sources/LegacyKit/, separating public and open symbols from internal ones.
- 03Run findReferences on each exported symbol to enumerate every callsite across the package's other targets and its XCTest cases.
- 04Pin behavior first: run the existing tests with bash using `swift test` and record the green XCTest baseline before changing anything.
- 05Add characterization XCTest cases in Tests/LegacyKitTests/ for any public function that currently has none, reviewing the unified diff Atlas surfaces before it is written.
- 06Restructure with apply_patch, which seeks each hunk's context and old_lines and fails with Failed to find context if the Swift file has drifted.
- 07Re-run `swift test` with bash after each hunk lands, not once at the end, so an XCTest failure points at the single hunk that caused it.
- 08Track the remaining callsites in a todowrite list, run `swift-format` over the touched Sources/ files, and finish with `swift build` so the compiler catches any caller you missed.
Frequently asked questions
- how to safely refactor a legacy Swift module without breaking callers
- Enumerate every callsite with Atlas's lsp tool findReferences before touching anything, record a green `swift test` baseline, restructure with apply_patch, and re-run XCTest after each hunk. Track remaining callsites in a todowrite list so a partial refactor is never mistaken for a finished one.
- does Atlas run swift test after every change
- The documented Swift refactor workflow re-runs the tests with bash after each hunk lands, not once at the end. Running `swift test` per hunk means an XCTest failure is attributable to a single structural change rather than to the whole refactor.
- what happens if my Swift file changed since Atlas wrote the patch
- apply_patch seeks each hunk's context and old_lines and fails with Failed to find context if the file has drifted. Atlas stops rather than writing the hunk at a wrong offset, which turns a possible silent corruption into a visible error.
- how do I find every caller of a public Swift protocol
- Run Atlas's lsp tool with findReferences on the protocol. The Swift language server returns callers across the package's targets and its Tests/ directory. Enumeration matters because `swift build` only checks callers inside your own Package.swift graph.
- can Atlas add XCTest cases before I refactor
- Yes. Ask Atlas to write characterization cases in Tests/, and it computes a unified diff for the new file and surfaces it for approval before writing. Running `swift test` on those cases first is what gives the refactor a real behavioral baseline.
- how do I undo an Atlas change to a Swift package
- Atlas snapshots file changes as git patches, so edits can be diffed and rolled back. Because apply_patch applies one anchored hunk at a time, a single structural change to Sources/ can be reverted without unwinding the rest of the refactor.
- will Atlas keep my Swift code formatted
- Run `swift-format` through Atlas's bash tool over the touched Sources/ files. swift-format is the formatter in the documented Swift setup, alongside XCTest via swift test as the test runner and Swift Package Manager as the package manager.
- how do I start Atlas in a Swift package
- Run atlas in a package with a Package.swift. Atlas reads your targets, protocols, and dependencies, and you can have it add XCTest cases or adopt async/await, reviewing the diff before it is written.
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 Swift in 2026
Atlas for Swift in 2026 empowers developers with a terminal-native AI coding agent. Index code by AST, ensure privacy with local embeddings, and review changes with unified diffs.
Run Atlas Headless in CI for Swift Projects in 2026
Learn how Swift developers in 2026 can run Atlas headless in CI pipelines to automate code tasks, generate XCTest cases, and get machine-readable output.
Locate where a behavior is implemented in Swift with Atlas (2026)
Find the Swift file and symbol behind a behavior in 2026 with Atlas: codebase_search for meaning, grep through ripgrep for exact text, and lsp for the symbol graph.
Automate GitHub Issue and Pull Request Triage in Swift with Atlas in 2026
Swift developers in 2026 can automate GitHub issue and pull request triage using Atlas, integrating with the Swift Package Manager toolchain for safe, trusted responses.
Plan a multi-file change before editing in Swift with Atlas (2026)
How to plan a multi-file Swift change in 2026 with Atlas: research with codebase_search and lsp in plan mode, write the plan markdown, then call plan_exit.
Run the Test Suite and Triage the Failures in Swift with Atlas (2026)
How to triage a red Swift suite with Atlas in 2026: run XCTest via swift test through bash, read the saved full log, group by root cause, and track fixes in todowrite.
Add a Regression Test for a Bug Fix in Swift with Atlas (2026)
Add a regression test for a bug fix in Swift with Atlas in 2026. Write a failing XCTest case, run swift test to prove it red, apply the fix, then re-run.