Refactoring with AI assistance is a workflow pattern, not a product feature. It matters when you’re staring at a codebase that has grown organically over years, where boundaries are fuzzy, names are inconsistent, and the cost of manual analysis exceeds the cost of automated probing. It also matters when you need to standardize patterns across a team without dedicating a full rewrite sprint. AI-assisted refactoring does not replace human judgment; it accelerates the exploration and scaffolding phases, provided you treat the output as a draft, not a delivery.
When the codebase is small enough to hold in a single developer’s head, or when the refactor is well-understood and mechanical (e.g., “replace all float with double”), AI adds friction. It adds value when the problem space is large, poorly documented, or spans multiple languages and repositories. The workflow described here works with any LLM backend accessible via HTTP—OpenAI, Anthropic, Gemini, Llamafile, whatever your organization runs—because the concrete commands below use standard tools: curl, jq, git, and shell pipelines. No vendor‑specific SDK or config key is required.
1. Map the codebase before you touch it
Before sending anything to an LLM, you need to know what you’re working with. The first concrete step is a structured inventory of the files, symbols, and imports that define the current shape.
# List every source file under the project root, excluding node_modules and .git
find . -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.go" -o -name "*.rs" \) \
-not -path "./node_modules/*" -not -path "./.git/*" | sort > /tmp/src-files.txt
# Extract every top‑level function/class name with its file and line number
awk '/^function /{file=FILENAME; gsub(/.*\//,"",file); print file":"$0}' /tmp/src-files.txt > /tmp/functions.txt
awk '/^class /{file=FILENAME; gsub(/.*\//,"",file); print file":"$0}' /tmp/src-files.txt >> /tmp/functions.txt
awk '/^def /{file=FILENAME; gsub(/.*\//,"",file); print file":"$0}' /tmp/src-files.txt >> /tmp/functions.txt
The result is a plain‑text list you can grep, sort, or pipe into other tools. If you’re working in a repository with a mix of languages, run the appropriate snippet for each and concatenate the outputs.
Once you have the list, use it to ask the LLM for high‑level observations.
# Build a compact summary of the codebase for the prompt
SUMMARY=$(head -n 200 /tmp/functions.txt | tr '\n' ' | ' | sed 's/ | $//')
# Send a concise analysis request to any LLM that accepts chat completions via HTTP
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"gpt‑4o-mini\",
\"messages\": [
{\"role\": \"system\", \"content\": \"You are a senior engineer analyzing a codebase.\"},
{\"role\": \"user\", \"content\": \"Here is a summary of the project’s functions and their locations:\n${SUMMARY}\n\nIdentify: (a) functions that appear in multiple files with similar names but different signatures, (b) import cycles that could be broken, (c) naming conventions that are inconsistent, and (d) any code that looks like a candidate for a strategy pattern or a data‑class migration. List each category with file references and line numbers.\"}
],
\"temperature\": 0.2
}" | jq -r '.choices[0].message.content'
What you’ll get back is a structured list the LLM noticed—duplicate‑named functions across files, import cycles flagged, naming anomalies, and pattern suggestions. This is the “map” you’ll use to decide what to refactor and in what order. Save the output; you’ll reference it when you start generating concrete changes.
2. Generate a refactor plan
With the map in hand, the next intent is to turn informal observations into a concrete plan: what to change, in what order, and with what safety nets. AI is useful here for synthesizing a step‑by‑step plan that respects dependencies.
# Re‑prompt the LLM with the explicit task of producing a plan
PLAN=$(curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"gpt‑4o-mini\",
\"messages\": [
{\"role\": \"system\", \"content\": \"You are a senior engineer producing a refactor plan.\"},
{\"role\": \"user\", \"content\": \"Based on the analysis output saved in analysis.txt, produce a refactor plan with exactly these columns: Step | Description | Files Affected | Risk Level | Rollback Command. Number the steps 1‑N. Risk Level must be one of Low/Medium/High. For each High‑risk step, include a git revert command that restores the original file. Keep the plan concise—no more than 12 steps.\"}
],
\"temperature\": 0.1
}" | jq -r '.choices[0].message.content')
The PLAN variable now contains a numbered list you can read, copy into a task tracker, or pipe into a script. Because the prompt constrains the output format, you get a machine‑parsable plan rather than free‑form prose. If the LLM declines or hallucinates file names, that’s a signal to revisit the map step—perhaps your initial inventory was incomplete.
3. Apply changes with git safety
The only way to use AI‑generated changes safely is to gate every mutation behind git. Never apply an LLM‑suggested change directly to main or a shared branch. The workflow is: create a branch, apply the change in a controlled way, run tests, and either merge or revert.
# Start from a clean state and branch off main (or whichever base you use)
git checkout main
git pull --ff-only
git checkout -b refactor/ai‑assisted-$(date +%Y%m%d%H%M)
# Example: the LLM suggested renaming `calculateTotal` to `computeSubtotal` across three files.
# Instead of blindly replacing, use a targeted sed pipeline that you can review before committing.
# Show what the change would look like without touching the working tree
for f in $(grep -l "calculateTotal" -- "src/a.py" "src/b.py" "src/c.py"); do
echo "=== $f ==="
grep -n "calculateTotal" "$f"
done
# Perform the rename only after visual confirmation, using git‑tracked substitution
# This pattern works for any token the LLM identified as needing a name change.
for f in src/a.py src/b.py src/c.py; do
sed -i 's/\bcalculateTotal\b/computeSubtotal/g' "$f"
git add "$f"
done
# Commit with a clear message that references the AI step
git commit -m "refactor: rename calculateTotal→computeSubtotal (AI‑assisted, step 3 of 7)"
If the rename touches public APIs or exported symbols, you’ll need to update call sites too. The same sed pattern applies: identify the token, confirm the scope, apply with sed -i, git add, git commit. Because the substitution is explicit and local to the branch, a git revert undoes the entire branch if anything breaks.
For more complex refactors—extracting a function, moving a class, changing a data structure—the same principle applies: generate the target code with the LLM, write it to a temporary file, diff it against the original, and only merge after the diff looks correct.
# Generate a replacement function body
curl -s https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"gpt‑4o-mini\",
\"messages\": [
{\"role\": \"system\", \"content\": \"You are a senior engineer writing production‑quality code.\"},
{\"role\": \"user\", \"content\": \"Rewrite the following Python function to use a dictionary‑based lookup instead of a chain of if/elif statements. Preserve the function signature and return type. Return only the new function body, no markdown, no explanations.\n\nOriginal:\n\ndef process_order(kind, items):\n if kind == \"A\":\n return sum(i * 1.1 for i in items)\n elif kind == \"B\":\n return sum(i * 0.95 for i in items)\n else:\n return sum(items)"}
],
\"temperature\": 0.0
}" | jq -r '.choices[0].message.content' > /tmp/new_func_body.py
# Diff the original against the generated body
git show HEAD:src/order.py | head -n 20 > /tmp/orig_func.py
diff /tmp/orig_func.py /tmp/new_func_body.py
The diff is your safety check. If the generated body uses patterns your team doesn’t allow (e.g., walrus operator in a Python 3.8+ environment, or imports that aren’t available), reject and re‑prompt. If the diff looks correct, write the new body into the file, stage, and commit.
4. Verify with tests
No refactor is complete without running the existing test suite. AI‑assisted changes can subtly alter behavior—edge‑case handling, rounding, default values—that tests will catch.
# Run the project’s test suite for the modified area
# Adjust the command to match your project’s test runner
npm test # Node.js / TypeScript
pytest # Python
go test ./... # Go
mvn test # Java (if applicable)
# If tests pass, merge the branch. If they fail, inspect the failure.
# A common failure mode with AI‑generated code: the LLM introduces a type mismatch,
# changes error handling semantics, or assumes a library version that isn’t installed.
# In that case, revert the branch and re‑prompt with more constraints.
When tests fail, the error message is your most concrete clue. Here’s what a typical failure looks like in a Python project:
TypeError: process_order() got an unexpected keyword argument 'kind'
That tells you the LLM either changed the signature or introduced a keyword argument that didn’t exist. Re‑prompt with a stricter instruction: “Preserve the exact function signature; do not add or remove parameters.”
5. Sharp edges — what silently fails
Even with the git‑first workflow, several things can go wrong without immediate notice:
- Hallucinated file names. The LLM may suggest renaming a function in a file that doesn’t exist, or referencing a module import that’s been removed. Always verify the file list before applying
sedor writing new code. - Context window truncation. If your codebase is large, the LLM may only see the first or last N lines of a file. Changes it proposes for the “bottom” of the file may be based on incomplete context, leading to syntactically valid but semantically wrong code.
- Import path drift. The LLM might generate code that imports
requestsormomentassuming they’re available, while your project pins those as optional dependencies or uses a different HTTP client. Check that every generated import exists inrequirements.txt,package.json, or the language’s module registry. - Test pollution. If your test suite uses fixtures or mocks that the LLM doesn’t understand, tests may pass locally but fail in CI because the AI generated code that depends on runtime state the mocks don’t provide.
- Silent behavioral changes. The most dangerous failures are those where the output is syntactically correct and tests pass, but the logic diverges. For example, an AI‑suggested “optimization” might change floating‑point rounding, or a regex substitution might silently drop edge cases. Always have a human trace the before‑and‑after behavior on a representative subset of inputs.
The only reliable antidote is human review at every gate: map generation, plan synthesis, code substitution, and test execution. Treat the LLM as a pair‑programmer who writes extremely fast but requires your scrutiny.
6. When to step back
AI‑assisted refactoring stops being efficient and starts being risky when:
- The codebase lacks any test coverage. Without tests, you have no feedback loop for whether the refactor changed behavior.
- The refactor spans more than a dozen files across multiple languages without a clear dependency map. At that point, the LLM’s context window becomes the de‑facto map, and errors compound.
- The organization’s compliance or security review requires an audit trail that includes who approved each change. AI‑generated diffs can be attributed, but the process is slower than a manual refactor with clear ownership.
In those cases, fall back to a traditional refactor: write the tests first, then change the code by hand. The AI workflow is a force multiplier, not a replacement for disciplined engineering practice.
Bottom line: AI‑assisted refactoring is a repeatable pattern—map, plan, apply under git, verify with tests, and inspect the sharp edges. Use curl and jq to talk to any LLM backend, git to enclosure every mutation, and diff + your test suite as the final gate. The commands above are concrete, vendor‑neutral, and ready to adapt to whatever tools your team already runs.
This page was rewritten on 11 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
Join the Newsletter
Get the latest updates and deep insights shipped directly to your inbox.