Refactoring prompts with type-safety locks

Migration plans, not migrations

4 min read

When the change spans more than one file or more than one language version, the right move is to ask for a plan first — not the migrated code. A plan is cheap to review. Migrated code spread across files is expensive to review and even more expensive to back out.

Here's a small Python 2 to 3.11 case:

Give me a 5-step migration plan to move this small Python 2 script to Python 3.11. Each step is one bullet. Then list 3 specific lines from the script that will break, with exact line content and the Python 3 replacement.

Script:

# inventory.py
def report(items):
    print "Found %d items" % len(items)
    for i, item in items.iteritems():
        print "%s: %s" % (i, item)

if __name__ == "__main__":
    raw = raw_input("Path: ")
    print "Reading", raw

Claude Sonnet 4.5 returned:

# 5-Step Migration Plan

- Update all `print` statements to use `print()` function syntax with parentheses
- Replace `raw_input()` with `input()` for user input
- Change dictionary `.iteritems()` method to `.items()` (which returns an iterator in Python 3)
- Test the script with Python 3.11 interpreter to verify functionality
- Update the shebang line (if present) to point to Python 3.11 and consider adding type hints for modernization

# 3 Specific Breaking Lines

Line 3:
- Python 2: `print "Found %d items" % len(items)`
- Python 3: `print("Found %d items" % len(items))`

Line 5:
- Python 2: `for i, item in items.iteritems():`
- Python 3: `for i, item in items.items():`

Line 9:
- Python 2: `raw = raw_input("Path: ")`
- Python 3: `raw = input("Path: ")`

Captured from Claude Sonnet 4.5 (claude-sonnet-4-5) on 2026-04-27. Re-runs may differ slightly.

The output is a one-page document you can use as a checklist. Each step is one PR if you want to break it up. Each "breaking line" is a verifiable claim — you can grep for iteritems and confirm the script has exactly one occurrence.

Now the actual migration becomes a separate prompt per step:

Apply step 2 from the plan: replace raw_input with input in inventory.py. Output a unified diff. Do not change anything else.

Each step is a small prompt with a small diff. Each diff is reviewable in five seconds. If a step looks wrong, you skip it without losing the others.

The plan-first pattern works at any scale. For a small script, the plan is five steps. For a real migration (Python 2 to 3 across 50 files, or React class components to hooks across a frontend), the plan looks the same — phases instead of steps, and each phase becomes its own PR.

The 5-step migration plan, visualised as a timeline:

Python 2 → 3.11 migration timeline

1
1. print → print()

All print statements get parens; one PR, easy to revert

2. raw_input → input

Replace input-reading calls; smallest semantic shift

3
3. iteritems → items

Dict iteration API change; touch every loop

4. Run on Python 3.11

Verify behaviour matches the Python 2 baseline

5. Modernise (optional)

Add type hints, pin python_requires>=3.11

Three patterns to copy from this prompt:

PatternWhat it does
Asking for both the plan AND specific breaking linesBridges abstract steps to verifiable claims
"Each step is one bullet"Forces atomic changes, not paragraphs
"Exact line content and replacement"Lets you grep before believing

A useful safety: when the plan claims "X lines will break," run the grep. If the count is off, the plan is partially wrong, and you should iterate before applying any of it. The model is fast but not always right about what's in your codebase — it's only ever working from what you pasted.

Next up: how to make the model write your PR description for you. :::

Quiz

Module 3: Refactoring Prompts

Take Quiz
Was this lesson helpful?

Sign in to rate

FREE WEEKLY NEWSLETTER

Stay on the Nerd Track

One email per week — courses, deep dives, tools, and AI experiments.

No spam. Unsubscribe anytime.