DEV Community

Dakota Huang
Dakota Huang

Posted on

Refactor a Messy Repo With a Diff Budget and One Frozen Seam

Most messy-repo refactors fail at review time, not at compile time. The diff grows, the reviewer guesses, and the team merges risk. The smallest safe change is a frozen seam plus a hard diff budget.

The rule: freeze behavior before you move code

  1. Pick one runtime seam. A seam is a function call you can test without the UI or database.
  2. Write a characterization test at that seam. Use real inputs, not invented ones.
  3. Record the current output, including quirks.
  4. Set a diff budget. Example: 20 changed production lines, two production files, zero deleted tests.
  5. Change one thing. Run the contract test and the budget check.
  6. Stop if the budget fails. Split the change.

This is not a purity rule. It is a review-capacity rule. AI coding tools make large patches cheap to produce. Review capacity did not grow with patch size.

The code below is a minimal example, not a report from a production repository.

Step 1: Find a seam you can test in one minute

Use git grep and coverage. Pick a function with a narrow input and a visible output. Avoid the function with ten globals.

git grep -n 'def send_receipt' -- app
python -m pytest --cov=app.receipts --cov-report=term-missing tests/ -q
Enter fullscreen mode Exit fullscreen mode

The example function reads a clock, a setting, and a mailer. That is three hidden dependencies. You do not refactor all three at once.

# app/receipts.py
from datetime import datetime
from django.conf import settings
from .mail import send_mail

def send_receipt(order):
    if order.total <= 0:
        return 'skipped'
    body = f'Order {order.id} total {order.total} at {datetime.now().isoformat()}'
    if settings.DRY_RUN:
        return 'dry-run'
    send_mail(order.email, 'Receipt', body)
    return 'sent'
Enter fullscreen mode Exit fullscreen mode

Step 2: Write the contract test before the change

Pin the current behavior with a test that calls only the public function. Use monkeypatch for the clock, setting, and mailer. The test is boring on purpose.

# tests/test_receipts_contract.py
from datetime import datetime, timezone
from types import SimpleNamespace
import pytest
import app.receipts as receipts

class FixedDateTime(datetime):
    @classmethod
    def now(cls, tz=None):
        return cls(2026, 11, 7, 12, 0, tzinfo=timezone.utc)

@pytest.fixture
def order():
    return SimpleNamespace(id='A-17', total=42, email='buyer@example.test')

def fail_mail(*args):
    raise AssertionError('mail sent')

def test_positive_order_sends_receipt(monkeypatch, order):
    sent = []
    monkeypatch.setattr(receipts, 'datetime', FixedDateTime)
    monkeypatch.setattr(receipts.settings, 'DRY_RUN', False, raising=False)
    monkeypatch.setattr(receipts, 'send_mail', lambda *args: sent.append(args))
    assert receipts.send_receipt(order) == 'sent'
    assert sent == [('buyer@example.test', 'Receipt', 'Order A-17 total 42 at 2026-11-07T12:00:00+00:00')]

def test_zero_total_is_skipped(monkeypatch, order):
    order.total = 0
    monkeypatch.setattr(receipts, 'send_mail', fail_mail)
    assert receipts.send_receipt(order) == 'skipped'

def test_dry_run_does_not_send(monkeypatch, order):
    monkeypatch.setattr(receipts, 'datetime', FixedDateTime)
    monkeypatch.setattr(receipts.settings, 'DRY_RUN', True, raising=False)
    monkeypatch.setattr(receipts, 'send_mail', fail_mail)
    assert receipts.send_receipt(order) == 'dry-run'
Enter fullscreen mode Exit fullscreen mode

This test is a contract, not a unit test of helpers. It survives the extraction. It also gives you a failure oracle.

Step 3: Prove the test can fail

Run the test once. Then change one branch and run it again. The test must fail. Revert the branch.

python -m pytest tests/test_receipts_contract.py -q
# temporarily change 'skipped' to 'sent'
python -m pytest tests/test_receipts_contract.py -q  # must fail
git checkout -- app/receipts.py
Enter fullscreen mode Exit fullscreen mode

A passing test with no failure history is a weak gate. One mutation is enough for this check. Keep it small and revert it immediately.

Step 4: Add a diff budget script

The budget turns review capacity into a command. The numbers are examples. Pick a budget your team can review in one sitting.

#!/usr/bin/env python3
'''Fail a refactor diff that is too large to review.'''
import argparse
import subprocess
import sys

def parse_numstat(text):
    prod_files, prod_lines, deleted_tests = [], 0, 0
    for row in text.splitlines():
        add, delete, path = row.split('\t', 2)
        if path.startswith('tests/'):
            if delete != '-' and int(delete) > 0:
                deleted_tests += int(delete)
            continue
        if path.startswith('scripts/') or path.endswith('.md'):
            continue
        prod_files.append(path)
        if add != '-' and delete != '-':
            prod_lines += int(add) + int(delete)
    return prod_files, prod_lines, deleted_tests

def main():
    p = argparse.ArgumentParser()
    p.add_argument('--max-lines', type=int, default=20)
    p.add_argument('--max-files', type=int, default=2)
    p.add_argument('--base', default='HEAD')
    a = p.parse_args()
    text = subprocess.check_output(['git', 'diff', '--numstat', a.base], text=True)
    files, lines, deleted_tests = parse_numstat(text)
    print(f'production files={len(files)} lines={lines} deleted_test_lines={deleted_tests}')
    if len(files) > a.max_files or lines > a.max_lines:
        print('BUDGET EXCEEDED: split the refactor before review.', file=sys.stderr)
        return 1
    if deleted_tests > 0:
        print('BUDGET EXCEEDED: deleted test lines.', file=sys.stderr)
        return 1
    return 0

if __name__ == '__main__':
    raise SystemExit(main())
Enter fullscreen mode Exit fullscreen mode

Run it before and after the change. Include new files with git add -N so the diff sees them.

git add -N app/receipts.py
python scripts/diff_budget.py --max-lines 20 --max-files 2
Enter fullscreen mode Exit fullscreen mode
Signal Example threshold Action
Production files changed > 2 Split the change
Production lines changed > 20 Stop and review
Deleted test lines > 0 Reject
Contract test failures > 0 Revert

This table is a guardrail, not a correctness proof. A small diff can still be wrong. A large diff is harder to review, so it needs a different plan.

Step 5: Make the smallest safe change

Inject the clock, setting, and mailer with defaults. Existing call sites keep working. The contract test stays unchanged.

# app/receipts.py after one seam change
from datetime import datetime
from django.conf import settings
from .mail import send_mail

def send_receipt(order, *, now=None, dry_run=None, mailer=None):
    if order.total <= 0:
        return 'skipped'
    now = now or datetime.now
    dry_run = settings.DRY_RUN if dry_run is None else dry_run
    mailer = mailer or send_mail
    body = f'Order {order.id} total {order.total} at {now().isoformat()}'
    if dry_run:
        return 'dry-run'
    mailer(order.email, 'Receipt', body)
    return 'sent'
Enter fullscreen mode Exit fullscreen mode

This patch touches one function and adds no call-site edits. The test can now pass an explicit clock later. That is a separate change.

If the patch changes a call site, reject it. If it reformats the file, reject it. If it renames helpers, reject it. The point is one seam, not a cleanup pass.

Step 6: Put a constrained model behind a stop condition

MonkeyCode is the project behind this outreach. Its operator describes it as an open-source project and states that the offer includes free model access and a free server option, including 10 million free tokens. Verify current terms before you rely on them.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

The product is relevant here only as a constrained patch runner. The workflow still stands without it. Use it to draft a unified diff from three inputs: the target file, the contract test, and the budget.

Use a narrow prompt:

Return a unified diff only.
Change one function in app/receipts.py.
Do not touch call sites, comments, imports, or formatting.
Preserve behavior for all existing callers.
The patch must fit 20 changed lines and 2 production files.
Enter fullscreen mode Exit fullscreen mode

Then run the two gates in a clean environment:

python -m pytest tests/test_receipts_contract.py -q
python scripts/diff_budget.py --max-lines 20 --max-files 2
Enter fullscreen mode Exit fullscreen mode

The free server option is useful for that clean run. The free model access is useful for the draft. Neither replaces the test or the budget. The model is not the authority.

If you test this workflow, run the two commands on the free server and keep the budget output with the diff.

Who should not use this workflow

  • Teams with no runnable test harness. You cannot freeze behavior you cannot execute.
  • Code that cannot leave your network. Use local tooling only.
  • Cross-module dependency cycles. That needs a design change, not one seam.
  • Regulated code with strict provenance rules. Get approval before using any external service.
  • Teams that need an SLA. A free option is not a support contract.

Limitations

A diff budget is a review guardrail, not a correctness proof. Characterization tests freeze current behavior, including current bugs. Fix bugs in a separate change. AI-generated patches can be syntactically valid and behaviorally wrong. Free offers can change, so verify the terms yourself. The budget numbers are examples, not universal constants.

The win is not a cleaner repo in one day. The win is a reviewable change with a failure oracle. Start with one seam, one contract test, and one budget check.

Top comments (0)