SwiftRelay

← All sprint notes

Green Light Checklist: Refactoring Legacy JavaScript

Agent quick startsEN

Map the Terrain Before the First Prompt

A legacy refactor goes wrong when an assistant is asked to improve code it has not been allowed to understand. Before you paste a single file, spend a short lap building a map of the codebase and hand that map to the model as context. That one habit removes most of the guesswork from every prompt that follows, and it keeps you from re-explaining the same module structure on every turn.

Ask for an inventory rather than edits. A useful opening prompt says: describe the structure of this codebase and list the riskiest areas, with no code changes yet. The answer is your working map, and it costs far less than discovering a hidden coupling halfway through a rename.

  • Entry points, module boundaries, and the files that everything else imports
  • Global mutable state, shared singletons, and anything stored on window or global
  • Duplicated business logic spread across several modules with slightly different behavior
  • Dead code, commented-out blocks, and branches that no test exercises
  • The build, bundler, and transpiler configuration that decides what actually ships

Treat the inventory as a living document. Correct the model where it is wrong, and keep the summary next to the code so later prompts can point at it instead of re-explaining it. A good map also tells you where not to touch anything yet, which is just as valuable as knowing where to start.

Lock Current Behavior with Characterization Tests

The fastest way to break a legacy system is to clean it while its behavior is only described in people's heads. Before any structural change, ask the assistant to write characterization tests that capture what the code does today, including the parts that look like bugs. Those tests are the harness that makes every later prompt safe to run.

Make the prompt explicit about intent: write tests that describe existing behavior, do not fix behavior, and do not refactor the code under test. If a test has to encode an oddity, that is the correct outcome. The moment an assistant silently corrects behavior while writing tests, your baseline is gone.

  • Happy paths through the main entry points and exported functions
  • Empty, null, and unexpected input shapes that callers already produce
  • Error handling, including the exact messages or codes downstream code depends on
  • Implicit coercions, date handling, and formatting quirks in older JavaScript
  • Paths guarded by feature flags, environment checks, or browser detection

When the suite passes against untouched code, you have a safety net. Every prompt that follows can end with the same instruction: after the change, run the test suite and report any behavior difference as a separate item rather than silently patching it. That single clause turns a risky rewrite into a sequence of observable steps.

Write Prompts That Bound the Refactor

Vague prompts produce vague diffs. A good refactoring prompt behaves like a small specification: it states the goal, the files in scope, the constraints, the non-goals, and the shape of the answer you want back. Write it the way you would write a ticket for a teammate who is very fast and very literal.

Structure each prompt around five parts. Name the target: refactor the authentication helpers in src/auth so that token parsing is isolated in one module. State the invariants: preserve the public API and every existing export name. List the constraints: keep the current syntax level, no new dependencies, no change to error messages. Declare the non-goals: do not rename files, do not add tests, do not tune performance. Then ask for a plan first: before editing, list the steps you intend to take and wait for confirmation.

  • Keep one concern per prompt, so a rename pass never mixes with a logic extraction
  • Request the diff in reviewable chunks instead of one large rewrite
  • Ask for inline reasoning only where a decision is non-obvious, so the output stays readable
  • Forbid drive-by reformatting, because noise in a diff hides real risk

Bigger context windows help when a module has deep cross-imports, and a strong general model such as Claude Sonnet 5.5 gives you room to paste several related files and still get a coherent extraction plan back. For a narrower task, such as pulling one pure function out of a tangle, a lighter model does the job just as well. Whatever you route to, the prompt discipline stays the same.

If you want to move between models mid-refactor without rewiring your tooling, SwiftRelay is a pay-as-you-go relay for Claude, GPT, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok and other models. It works with Claude Code, Codex, Gemini CLI and any OpenAI-compatible SDK, so your existing prompts and scripts keep working while you change engines. Billing is per token, which suits refactoring work because a session of long context and many small diffs costs exactly what it consumes. Point Claude Code or Gemini CLI at https://api.fastapirelay.com as the bare host, and add /v1 for OpenAI SDKs. New accounts get a small gift credit, and inviting friends earns 14% of their top-ups. Sign up once and put your next refactoring lap through it.

Stage the Sweep in Small, Reviewable Passes

Treat a big cleanup as a sequence of pit stops rather than one heroic rewrite. Each pass should leave the codebase working and the tests green before the next one begins, so a bad step is cheap to undo and easy to isolate.

Ask for a numbered plan, then execute it one step at a time. A reliable order is extraction first, then rename, then deletion. Mechanical changes and behavior-affecting changes belong in separate passes, because a reviewer can only judge one kind of risk at a time. Commit after every passing step, and stop to re-plan when the diff grows past what you can read in one sitting.

  • Extract the pure logic before touching anything with side effects
  • Move code first and change code second, so diffs stay explainable
  • Delete dead paths only after the tests prove nothing calls them
  • Keep a short running note of decisions made during the session

Review every diff with three questions in mind. Does it do only what the prompt asked? Does it preserve the public surface? Could a teammate understand the change without reading the chat log? If the answer to any of them is no, send the diff back with the specific complaint rather than fixing it by hand, so the assistant learns the boundary.

Verify, Document, and Hand Over Clean Code

The last lap is verification and handover, and it is where most assisted refactors quietly fail. Code that looks tidy in the diff still ships to real callers, so the checks you run here carry more weight than any prompt you wrote earlier.

  • Run the full test suite together with linting and static checks where they exist
  • Compare the exported API surface against the snapshot taken before the refactor
  • Search for stale references, old helper names, and imports that now dangle
  • Ask for a summary of removed code and of any behavior that changed, however slightly

Then ask the assistant to write the handover notes: what changed, why it changed, what was deliberately left alone, and which follow-up tasks you queued instead of doing in this session. Store those notes in the repository, not in the chat, so the next developer inherits the context that you built. When the tests pass, the API is unchanged, and the notes are checked in, you have a refactor you can defend months later, regardless of which model wrote the first draft.

Keep reading

Back for another lap?Log in