Atlas refactors a legacy JavaScript module by mapping its public surface with the lsp tool's documentSymbol operation, running findReferences on each exported symbol to enumerate every callsite, recording a green baseline by running vitest through bash, restructuring with apply_patch, and re-running vitest after each hunk lands. The risk in a refactor is silent breakage at a callsite you did not know about, and Atlas closes that gap before it edits a single line of src/legacy/report.js.
How does Atlas refactor a legacy JavaScript module without breaking callers?
Atlas refactors a legacy JavaScript module in 5 documented steps: map the module's public surface with the lsp tool's documentSymbol operation, run findReferences on each exported symbol, record a green vitest baseline through bash, restructure with apply_patch, and re-run vitest after each hunk lands.
The order matters. Atlas does not open src/legacy/report.js and start rewriting. It first asks the JavaScript language server for documentSymbol on that file, which returns the real exported surface: the named exports, the default export, the classes. Then findReferences on each of those symbols produces the authoritative callsite list, which in a JavaScript repo routinely includes a barrel src/index.js re-export, a test file under test/, and an import inside a bundler config that a text search would never associate with the function. Only once that list exists does Atlas touch code, and it uses apply_patch to do it.
What is the green vitest baseline and why does Atlas record it first?
Atlas pins behavior before changing it, step 2 of the documented refactor workflow, by running pnpm vitest run through the bash tool and recording the passing baseline. A refactor is a change that does not alter behavior, so a JavaScript refactor without a recorded green vitest run cannot prove that definition held.
In practice Atlas runs the suite with bash, using a generous timeout in milliseconds so a large vitest run is not killed mid-execution, and keeps the pass count. That number is the contract. If vitest reported 214 passing before the refactor and 213 after, the refactor changed behavior and the work is not done. Because bash is a real shell, Atlas can also narrow the baseline to the module under refactor with vitest's own path filter, which makes the feedback loop after each apply_patch hunk fast enough to actually run every time rather than once at the end.
Why does Atlas use apply_patch instead of rewriting the JavaScript file?
Atlas restructures legacy JavaScript with apply_patch, step 3 of the documented workflow, because apply_patch anchors each hunk on surrounding context lines and old_lines and fails with Failed to find context if src/legacy/report.js has drifted since Atlas read it. A whole-file rewrite silently discards drift, and apply_patch refuses to.
Drift is common in a JavaScript repo: a prettier run reformats the file, a teammate lands a hotfix, or an editor plugin reorders imports between the moment Atlas read the module and the moment it writes. apply_patch treats that as an error rather than an opportunity to clobber. Each hunk carries its context, and if the context is gone the patch does not apply. Atlas then re-reads the file and rebuilds the patch against the current content. For a legacy module being restructured over a dozen hunks, that property is what makes the sequence safe to run unattended between vitest runs.
How do you track partially migrated callsites in a JavaScript refactor?
Atlas tracks the remaining JavaScript callsites in a todowrite list, one entry per importer that findReferences turned up, so a module that is half migrated cannot be mistaken for a finished one. A legacy module with 9 importers is not refactored until all 9 entries are closed.
The failure mode this prevents is specific and familiar. You restructure src/legacy/report.js, update the three obvious importers, run pnpm vitest, see green because the other importers are not covered by tests, and ship a broken build. Atlas writes each callsite from the findReferences output into a todowrite entry with status pending, marks each one completed as the corresponding apply_patch lands and vitest passes, and the list stays visible in the session. The list, not your memory, is the record of what is left.
How does Atlas review and approve JavaScript refactor edits before they land?
Atlas computes a unified diff for every file edit and surfaces it for approval before writing, and every Atlas tool call is permission-gated against 3 rule types, allow, ask, and deny, before it runs. A refactor of src/legacy/report.js therefore lands as a reviewed diff, never as an unannounced write.
You see the hunk, in unified diff form, before it touches the working tree. The same gate applies to the bash calls that run pnpm vitest and to any prettier invocation, so nothing executes outside your allow rules. Atlas also snapshots file changes as git patches, which means a refactor that goes sideways three hunks in can be diffed and rolled back rather than manually unpicked. Combined with apply_patch's refusal to apply against a drifted file, the safety story for a legacy JavaScript refactor is that every change is reviewable, revertible, and verified by a real vitest run.
Can Atlas find the JavaScript module's real structure without reading every file?
Atlas indexes JavaScript code by AST declarations using tree-sitter, not blind line windows, and searches with hybrid semantic and keyword retrieval fused by reciprocal rank fusion. A 4000 line legacy module is retrieved as its actual functions and classes, not as arbitrary 50 line chunks.
That indexing choice is what makes the read step cheap on a legacy JavaScript codebase. When Atlas needs the implementation of a helper buried in src/legacy/report.js, the retrieved unit is the declaration, so the model sees a whole function rather than a fragment sliced through the middle of a closure. If you would rather the code never leave your machine, Atlas can build the same index with local Ollama embeddings. Either way, the index is what lets Atlas propose an accurate apply_patch on the first attempt instead of guessing at surrounding context that does not exist.
Step by step
- 01Run atlas where your package.json lives, and let Atlas map your modules, npm scripts, and bundler config.
- 02Have Atlas call the lsp tool's documentSymbol operation on the legacy module, for example src/legacy/report.js, to map its public surface.
- 03Run findReferences on each exported symbol to enumerate every callsite, including barrel re-exports from src/index.js that a plain grep would miss.
- 04Pin behavior first: have Atlas run pnpm vitest run through the bash tool and record the green baseline pass count before changing anything.
- 05Restructure with apply_patch, which seeks each hunk's context and old_lines and fails with Failed to find context if the file has drifted.
- 06Re-run vitest with bash after each hunk lands, not once at the end, so the hunk that broke behavior is the one you just applied.
- 07Track the remaining callsites in a todowrite list so a partially migrated module cannot be mistaken for a finished one.
- 08Run prettier over the touched files, then review the unified diff Atlas surfaces before approving the final write.
Frequently asked questions
- how to refactor legacy JavaScript code with an AI agent
- Run atlas where your package.json lives. Atlas maps the module's exports with the lsp tool's documentSymbol operation, enumerates callsites with findReferences, records a green vitest baseline through bash, then restructures with apply_patch and re-runs vitest after each hunk.
- will an AI coding agent break my callers when refactoring a JavaScript module
- Atlas enumerates every callsite with the lsp tool's findReferences before it touches the file, tracks them in a todowrite list, and re-runs pnpm vitest after each apply_patch hunk. Every edit is surfaced as a unified diff for approval before it is written.
- does Atlas work with pnpm and vitest
- Yes. Atlas runs pnpm and vitest through its bash tool, which is a real shell, so the same commands you would type by hand are the ones Atlas runs. It reads your package.json, npm scripts, and bundler config when you start it in the repo root.
- what happens if the JavaScript file changed while Atlas was working on it
- apply_patch anchors on each hunk's context lines and old_lines, so a drifted file produces Failed to find context rather than a bad write. Atlas re-reads the file and rebuilds the patch against the current content.
- can Atlas run prettier as part of a refactor
- Yes. Atlas invokes prettier through the bash tool like any other command, subject to your allow, ask, and deny permission rules. Every tool call is permission-gated before it runs, so prettier does not execute unless your rules permit it.
- how do I undo an Atlas refactor that went wrong
- Atlas snapshots file changes as git patches, so edits can be diffed and rolled back. It also reads git branches, status, and diffs, and can stage and create commits on your behalf once the vitest suite is green again.
- can Atlas index my JavaScript codebase without sending code to a third party
- Yes. Atlas can build its code index with local Ollama embeddings, keeping code off third-party servers. The index is built from AST declarations using tree-sitter, so retrieval returns whole functions and classes rather than blind line windows.
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 JavaScript in 2026
In 2026, Atlas empowers JavaScript developers with a terminal-native AI coding agent. It indexes code by AST, uses local embeddings, and offers permission-gated tools for safe, efficient development.
Trace a Runtime Bug from a Stack Trace in JavaScript with Atlas (2026)
Go from a production JavaScript stack trace to the responsible line in 2026 with no debugger attached. Atlas reads each frame at its offset and pins the fix with vitest.
Run Atlas Headless in CI in a JavaScript Project (2026)
Run Atlas non-interactively in a JavaScript pipeline. `atlas run` sends one prompt, streams events to stdout, supports --format json, and exits when the session goes idle.
Rename a symbol across the repo in JavaScript with Atlas (2026)
Rename a JavaScript function, class, or constant repo-wide in 2026 with Atlas: lsp findReferences for real callsites, grep for strings and docs, edit with replaceAll.
Debug a Single Failing Test in JavaScript with Atlas (2026)
Debug one failing JavaScript test with Atlas in 2026: run it in isolation with vitest through pnpm, walk the call path with the lsp tool, and fix the code, not the assertion.
Review a Pull Request in JavaScript With Atlas (2026 Guide)
How to review a JavaScript pull request with Atlas in 2026: bash produces the raw diff, read pulls whole files, and lsp findReferences catches the callers the diff hides.
Diagnose a Hanging or Long-Running JavaScript Command with Atlas (2026)
Is your pnpm or vitest command slow or silently blocked on input? In 2026 Atlas races every command against a timeout and tells you which of the two it was.