Skip to main content

Surgical code grafting — migrate functions between files with zero LLM token waste

Project description

surgraft

Surgical code grafting. Spend tokens only on what changed.

When refactoring a large file, most LLM tools read a function and then rewrite it into the new file — burning tokens on code that didn't change. surgraft separates the two concerns:

  1. Locate — find function boundaries using AST parsing (zero tokens)
  2. Copy — shell-level byte copy into the new file (zero tokens)
  3. Edit — LLM sees only the extracted snippet (tokens ∝ delta, not file size)
5,000-line file, 20-line function, naive LLM rewrite: ~5,000 tokens
surgraft copy-only:                                       0 tokens
surgraft + edit pass:                                   ~25 tokens

Install

pip install surgraft                   # copy-only (no API key needed)
pip install "surgraft[llm]"            # + LLM edit pass via Anthropic

Usage

List symbols in a file

surgraft list old_service.py
old_service.py  (4,821 lines)

  SYMBOL                                   KIND          LINES
  ────────────────────────────────────────────────────────────
  AuthService                              class         42–310   (269L)
  AuthService.validate_token               function      58–89    (32L)
  AuthService.refresh_session              function      91–134   (44L)
  parse_jwt                                function      312–341  (30L)
  hash_password                            function      343–367  (25L)
  ...

  23 symbols

Graft symbols — zero tokens

Copy functions into a new file without touching the LLM at all:

surgraft graft old_service.py new_auth.py \
  --symbols "validate_token,refresh_session,parse_jwt"
→ Source   old_service.py
→ Dest     new_auth.py

✓ Grafted   validate_token    lines 58–89    → new_auth.py
✓ Grafted   refresh_session   lines 91–134   → new_auth.py
✓ Grafted   parse_jwt         lines 312–341  → new_auth.py

  Tokens spent on copy: 0  (naive rewrite ≈ 19,284 tokens)

Graft + edit — tokens only on the delta

Copy and transform in one step. The LLM sees only the extracted snippet:

surgraft graft old_service.py new_auth.py \
  --symbols "validate_token,refresh_session" \
  --edit "convert to async/await, replace self.db with db: AsyncSession parameter"
→ Source   old_service.py
→ Dest     new_auth.py
→ Edit     'convert to async/await...'

→ Editing  validate_token    (~142 tokens)
✓ Grafted (edited)   validate_token  → new_auth.py
→ Editing  refresh_session   (~176 tokens)
✓ Grafted (edited)   refresh_session → new_auth.py

  Token usage: 318 tokens used  (naive rewrite ≈ 19,284 tokens, saved ~98%)

Graft all symbols

surgraft graft old_service.py new_service.py --symbols ALL

Check migration progress

See what's been moved and what's still outstanding:

surgraft diff old_service.py new_service.py
  Migration gap: old_service.py → new_service.py

  NOT YET GRAFTED
    ✗ parse_jwt                              30L
    ✗ hash_password                          25L
    ✗ AuthService.rotate_key                 18L

  ALREADY PRESENT
    ✓ validate_token
    ✓ refresh_session

  3 remaining / 2 done / 5 total

Dry run

Preview what would be copied without writing anything:

surgraft graft old.py new.py --symbols parse_jwt --dry-run

How it works

┌─────────────────────────────────────────────────────────┐
│                      surgraft graft                      │
└────────────────────┬────────────────────────────────────┘
                     │
         ┌───────────▼───────────┐
         │   AST / regex parser  │  ← zero tokens
         │  finds line boundaries │
         └───────────┬───────────┘
                     │
         ┌───────────▼───────────┐
         │    Shell byte copy    │  ← zero tokens
         │  sed -n 'N,Mp' → dest │
         └───────────┬───────────┘
                     │
              --edit provided?
                     │
          YES ───────▼────────  NO → done
         ┌───────────────────┐
         │  LLM edit pass    │  ← tokens ∝ snippet, not file
         │  sees: snippet    │
         │  + instruction    │
         │  outputs: snippet │
         └───────────────────┘

Supported languages

Language Method Accuracy
Python ast module Exact
JavaScript / TypeScript Regex heuristic Good
JSX / TSX Regex heuristic Good

API

Use surgraft as a library:

from surgraft.extractor import extract_symbols, find_symbol, graft_lines, read_lines
from surgraft.llm import edit_snippet

# find all symbols
symbols = extract_symbols("old_service.py")

# locate one
sym = find_symbol("old_service.py", "validate_token")
print(sym.start_line, sym.end_line)  # 58 89

# copy — zero tokens
graft_lines("old_service.py", sym.start_line, sym.end_line, "new_auth.py")

# edit — tokens ∝ snippet only
snippet = read_lines("old_service.py", sym.start_line, sym.end_line)
edited = edit_snippet(snippet, "make this async")

with open("new_auth.py", "a") as f:
    f.write(edited)

Configuration

Set your API key for the edit pass:

export ANTHROPIC_API_KEY=sk-ant-...

Or pass it inline:

surgraft graft old.py new.py --symbols fn --edit "..." --api-key sk-ant-...

Why not just use an LLM agent?

LLM agents do solve refactoring — but they solve it by reading and rewriting. For a 5,000-line file with 50 functions, that's 50 × ~5,000 = 250,000 tokens spent transcribing unchanged code. surgraft treats the LLM as a transformer, not a copier. The bytes that didn't change never touch the model.


Contributing

git clone https://github.com/your-name/surgraft
cd surgraft
pip install -e ".[dev]"
pytest

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

surgraft-0.1.0.tar.gz (20.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

surgraft-0.1.0-py3-none-any.whl (13.2 kB view details)

Uploaded Python 3

File details

Details for the file surgraft-0.1.0.tar.gz.

File metadata

  • Download URL: surgraft-0.1.0.tar.gz
  • Upload date:
  • Size: 20.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for surgraft-0.1.0.tar.gz
Algorithm Hash digest
SHA256 99e54d6da649723d655850a7433dae68b3b149ef99b006dab283c90f8e1c68f3
MD5 fb2899d5c5218c53b66d610999f0290e
BLAKE2b-256 13e49fa0983bdbf68e43df019b413cf0906cdaa4f8bc8d36e08c8a117cd6b974

See more details on using hashes here.

File details

Details for the file surgraft-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: surgraft-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 13.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.6

File hashes

Hashes for surgraft-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 01b05742658f21c5e9697ebf01b9e895bb339d3dd708af33e19fa61aaeeee4c6
MD5 6ec2715c783f2912cdb4aac8af18c79c
BLAKE2b-256 2942cfc3d7afce307e18990a9d67bc129a8ed80b9dc07eec446dc8ca8ae2a1cd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page