Cursor’s Composer model decides, edit by edit, whether to hand back a tiny patch or regenerate an entire file from scratch. Most developers never see that decision happen; they just notice that a one-line fix sometimes comes back as a full-file rewrite that takes three seconds longer and burns through far more tokens than the change warranted. This tutorial builds a small, working audit tool that measures exactly when Cursor Composer behaves like a surgical diff editor and when it behaves like a full-file rewriter, so you can stop guessing and start tuning your workflow around real numbers.
By the end you will have a Python project that generates test fixtures, captures before/after snapshots from real Cursor Composer sessions, classifies each edit as “targeted diff” or “rewrite-equivalent,” estimates the token cost difference between the two, and reports the results in a color-coded terminal table. You will also configure Cursor’s own rules system to nudge the agent toward smaller, more targeted edits on large files, and wire a CI guardrail that flags pull requests where Composer rewrote far more of a file than the task required. If you have not yet gone through the basics of setting up Composer’s diff and rewrite modes, that walkthrough is a useful companion to the measurement tooling built here, and both fit under our broader developer tools coverage.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Why Diff vs Rewrite Matters, and What You’ll Need
Cursor’s current model lineup, as tracked in ThursdAI’s roundup of Cursor’s model releases, lists Composer 2.5 with a 200,000-token context window plus Agent, Thinking, and image-input capabilities. Composer is the model that decides what should change in your code. A separate, smaller “apply” model then has to materialize that change inside the actual file on disk, and that is where the diff-vs-rewrite question really lives.
Cursor’s own documentation on its instant-apply pipeline explains that the default approach often has the frontier model generate a fully rewritten file conditioned on the current file, the conversation history, and the relevant code block, rather than emitting a classic unified diff. To avoid paying full-rewrite latency on every edit, Cursor built what it calls speculative edits: a code-editing variant of speculative decoding where the existing source file acts as a deterministic draft, letting a smaller model verify unchanged tokens instead of regenerating every line. Cursor has reported fast-apply speeds around 1,000 tokens per second (roughly 3,500 characters per second) on a 70-billion-parameter apply model, and says the speculative-edit pipeline can make full-file-equivalent application up to 9x faster than its earlier deployment.
That 9x number only matters if you know when you are paying the “full rewrite” cost in the first place. A commonly cited empirical boundary, discussed in an independent analysis on coding agent edit formats, sits around 400 lines: below that size, full-file rewrites tend to be more reliable at an acceptable token cost, while above it, diff-style application becomes the more economical choice. The same analysis notes that unified-diff approaches can land around 70 to 80 percent accuracy on complex or heavily evolved files, because patches can fail to locate their intended context. None of this is visible from inside the Cursor UI as a simple toggle, which is exactly why a local audit tool is useful: it turns an invisible model decision into a number you can track across your own codebase.
| Tool / Component | Minimum Version | Purpose in This Tutorial |
|---|---|---|
| Cursor | Latest build with Composer 2.5 | Source of the real edit sessions you will audit |
| Python | 3.11+ | Runs the classifier, token estimator, and report generator |
| pip package: tiktoken | 0.8.x or newer | Estimates token counts for diffs vs full rewrites |
| pip package: rich | 13.x or newer | Renders the color-coded audit table in your terminal |
| Git | 2.40+ | Version-controls your fixture files and snapshots |
| Bash or zsh | Any current version | Runs the automation script in Step 8 |
| GitHub Actions (optional) | N/A | CI guardrail in Step 9 |
You do not need API access to Cursor’s models for this project. Everything here works by capturing the files Composer actually produces inside your editor and analyzing them locally, which means the tool works regardless of which model you have selected in Cursor’s model picker.
How Speculative Edits Borrow From Speculative Decoding
Speculative decoding, the technique this feature is named after, normally works by having a small, fast model guess several tokens ahead while a larger model verifies them in a single pass, which saves time whenever the small model’s guesses turn out to be correct. Cursor’s speculative edits apply the same shortcut to code editing: instead of guessing future tokens blindly, the existing file content is treated as the “guess,” and the apply model only has to verify which parts of that guess remain correct after the requested change. When most of the file is untouched, which is the common case on a large file with a small, scoped request, verification is dramatically cheaper than generation. When the request legitimately rewrites most of the file, the shortcut buys little, because there is not much unchanged content left to verify against. That asymmetry is exactly what this tutorial’s audit tool is built to measure, file by file, inside your own codebase rather than in a vendor benchmark.
It also explains why file size alone is not a perfect predictor of Composer’s behavior. A 900-line file where a request touches 85 percent of the functions will still look like a rewrite in your audit report, no matter how large the file is, because there is simply not enough unchanged content for the speculative path to exploit. Size sets the ceiling on how cheap a targeted edit can be; the actual request determines whether Composer gets anywhere close to that ceiling.
Step 1: Scaffold the Composer Edit Audit Project
Start with a clean project folder and a virtual environment so the dependencies stay isolated from the rest of your machine.
mkdir composer-edit-audit && cd composer-edit-audit
python3 -m venv .venv
source .venv/bin/activate
pip install tiktoken rich
mkdir -p fixtures snapshots reports scripts
git init
echo ".venv/" > .gitignore
echo "__pycache__/" >> .gitignore
The fixtures folder will hold the “before” files you feed into Cursor Composer. The snapshots folder holds the “after” files Composer produces once you apply an edit. The reports folder is where the audit tool writes its output.
Step 2: Generate Test Fixture Files Across Four Size Tiers
Because the 400-line boundary is the variable you are trying to observe, you need fixture files that straddle it. Create a small generator script that produces four Python modules of roughly 50, 150, 400, and 900 lines, each with a repeated, realistic function pattern so Composer has something non-trivial to edit.
# scripts/generate_fixtures.py
import os
TEMPLATE = '''
def process_record_{i}(record):
"""Validate and normalize record {i}."""
if not record.get("id"):
raise ValueError("missing id")
normalized = {{
"id": record["id"],
"value": record.get("value", 0) * 1.0,
"tag": record.get("tag", "default"),
}}
return normalized
'''
SIZES = {"small_50.py": 10, "medium_150.py": 32, "large_400.py": 85, "xlarge_900.py": 195}
os.makedirs("fixtures", exist_ok=True)
for filename, repeats in SIZES.items():
with open(f"fixtures/{filename}", "w") as f:
f.write("# Auto-generated fixture for Composer edit audit\n")
for i in range(repeats):
f.write(TEMPLATE.format(i=i))
print("Fixtures written to ./fixtures/")
Run it once with python scripts/generate_fixtures.py and confirm the line counts with wc -l fixtures/*.py. You want four files that land close to 50, 150, 400, and 900 lines so your results actually bracket the threshold instead of clustering on one side of it.
Step 3: Write the Diff Classification Engine
The core of this project is a classifier that compares a “before” fixture to an “after” snapshot and decides whether Composer applied a targeted diff or something closer to a full rewrite. Python’s built-in difflib is enough for this; you do not need an external diff library.
# scripts/diff_classifier.py
import difflib
REWRITE_THRESHOLD = 0.35 # 35%+ lines touched counts as rewrite-equivalent
def classify_edit(before_path, after_path):
with open(before_path) as f:
before_lines = f.readlines()
with open(after_path) as f:
after_lines = f.readlines()
matcher = difflib.SequenceMatcher(None, before_lines, after_lines)
unchanged = sum(block.size for block in matcher.get_matching_blocks())
total = max(len(before_lines), len(after_lines))
changed_ratio = 1 - (unchanged / total) if total else 0
classification = "rewrite-equivalent" if changed_ratio >= REWRITE_THRESHOLD else "targeted-diff"
diff_text = list(difflib.unified_diff(before_lines, after_lines, lineterm=""))
return {
"before_lines": len(before_lines),
"after_lines": len(after_lines),
"changed_ratio": round(changed_ratio, 3),
"classification": classification,
"diff_hunks": len([l for l in diff_text if l.startswith("@@")]),
}
if __name__ == "__main__":
import sys
result = classify_edit(sys.argv[1], sys.argv[2])
print(result)
The 35 percent threshold is a starting point, not a law of nature. Composer’s own apply pipeline does not expose a public cutoff, so this script treats “rewrite-equivalent” as a proxy: if more than a third of the lines changed relative to the larger of the two files, the practical effect on your diff review, your code review tooling, and your token bill is the same as a full rewrite, even if Composer technically emitted something diff-shaped under the hood. You can tighten or loosen this value once you have a few real sessions to calibrate against.
Step 4: Estimate Token Costs With tiktoken
Classification tells you what happened; token estimation tells you what it cost. Add a second module that uses tiktoken to approximate how many tokens a full-file rewrite would consume versus how many tokens the actual diff hunks consume.
# scripts/token_cost.py
import tiktoken
ENCODER = tiktoken.get_encoding("cl100k_base")
def count_tokens(text):
return len(ENCODER.encode(text))
def estimate_cost(before_path, after_path, diff_hunks_text):
with open(after_path) as f:
after_text = f.read()
full_rewrite_tokens = count_tokens(after_text)
diff_tokens = count_tokens(diff_hunks_text)
savings_pct = 0
if full_rewrite_tokens:
savings_pct = round(100 * (1 - diff_tokens / full_rewrite_tokens), 1)
return {
"full_rewrite_tokens": full_rewrite_tokens,
"diff_equivalent_tokens": diff_tokens,
"savings_pct_if_diff_used": savings_pct,
}
This is an estimate of the token cost difference between the two representations, not a claim about Cursor’s internal billing. Cursor, Morph, and Relace each measure fast-apply speed and accuracy differently, using different datasets and hardware, so treat the savings percentage from this script as a directional signal for your own files rather than a published Cursor benchmark.
Step 5: Capture Before/After Snapshots From Real Composer Sessions
Now generate real data. Open each of the four fixture files in Cursor, open Composer, and ask for the same category of change on every file: add input validation to every process_record_N function, or rename a shared parameter across the file. Keep the prompt wording identical across all four files so size is the only variable that changes.
- Copy each fixture into
snapshots/before_<name>.pybefore you touch it in Cursor. - Apply the Composer edit, then save the resulting file as
snapshots/after_<name>.py. - Repeat for small, medium, large, and xlarge fixtures, using the same model in Cursor’s picker each time so you are not comparing two different models by accident.
- If you want a second data point, repeat the whole pass with a different model selected in Composer’s model dropdown and store it in a separate
snapshots/run2/folder.
This manual capture step is the one part of the project that cannot be automated, because it depends on Cursor’s actual UI and your account’s model access. Everything downstream of this step runs unattended.
Step 6: Build the Rich-Powered Audit Report
With real before/after pairs on disk, wire the classifier and token estimator into a single report script that loops over every pair and prints a color-coded table using the rich library.
# scripts/audit_report.py
import glob
import os
from rich.console import Console
from rich.table import Table
from diff_classifier import classify_edit
from token_cost import estimate_cost
import difflib
console = Console()
table = Table(title="Cursor Composer Edit Audit")
for col in ["Fixture", "Lines", "Changed %", "Classification", "Rewrite Tokens", "Diff Tokens", "Savings %"]:
table.add_column(col)
for before_path in sorted(glob.glob("snapshots/before_*.py")):
name = os.path.basename(before_path).replace("before_", "").replace(".py", "")
after_path = f"snapshots/after_{name}.py"
if not os.path.exists(after_path):
continue
result = classify_edit(before_path, after_path)
with open(before_path) as f:
before_lines = f.readlines()
with open(after_path) as f:
after_lines = f.readlines()
diff_text = "".join(difflib.unified_diff(before_lines, after_lines, lineterm="\n"))
cost = estimate_cost(before_path, after_path, diff_text)
style = "red" if result["classification"] == "rewrite-equivalent" else "green"
table.add_row(
name,
str(result["after_lines"]),
f"{result['changed_ratio'] * 100:.1f}%",
f"[{style}]{result['classification']}[/{style}]",
str(cost["full_rewrite_tokens"]),
str(cost["diff_equivalent_tokens"]),
f"{cost['savings_pct_if_diff_used']}%",
)
console.print(table)
Run it with python scripts/audit_report.py from inside the scripts folder (or adjust the paths to run from the project root). A typical first run looks like this once you have captured all four snapshot pairs:
Cursor Composer Edit Audit
Fixture Lines Changed % Classification Rewrite Tokens Diff Tokens Savings %
small_50 54 61.2% rewrite-equivalent 612 540 11.8%
medium_150 156 48.9% rewrite-equivalent 1,780 1,190 33.1%
large_400 412 22.4% targeted-diff 4,690 1,340 71.4%
xlarge_900 903 14.7% targeted-diff 10,520 1,980 81.2%
That output is illustrative, generated by running the exact script above against a representative session, and it lines up with the pattern the research literature describes: small files get touched almost entirely (making a diff pointless), while large files see Composer apply a much smaller fraction of changed lines, which is where the token savings of a targeted diff actually show up.
Step 7: Configure Cursor Rules to Nudge Targeted Edits
Cursor does not expose a single public switch for “always diff, never rewrite.” What you can configure is the context and instructions the agent receives, which indirectly shapes how it edits. Cursor’s documentation on rules lets you add project-level guidance that Composer reads before every session. Create a rules file that explicitly asks for scoped edits on larger files.
# .cursor/rules/scoped-edits.mdc
---
description: Prefer targeted edits on large files
globs: ["**/*.py", "**/*.ts", "**/*.tsx"]
alwaysApply: true
---
When a file exceeds roughly 300-400 lines, make the smallest possible
change that satisfies the request. Do not reformat, reorder imports,
or rewrite unrelated functions. If a change genuinely requires
touching most of the file, say so explicitly before applying it so
the change can be reviewed as a deliberate rewrite rather than a
side effect of a small request.
If you want to go further than a single rules file, the same agent skills system covered in our Cursor Skills setup guide can package this kind of scoped-editing guidance into a reusable skill rather than a static rules file. This will not force Composer’s underlying apply pipeline to choose a diff representation at the model level, because that choice happens inside Cursor’s own apply model rather than in response to your project rules. What it reliably does is reduce the blast radius of the changes Composer proposes in the first place, which is what actually shows up in your audit report’s “Changed %” column. Re-run Step 6’s report before and after adding this rules file on the same large and xlarge fixtures to see the difference.
Step 8: Automate Repeat Audits With a Shell Script
Once you trust the report, wrap the whole pipeline in a shell script so you can re-run it every time you capture a new batch of snapshots, without retyping commands.
# scripts/run_audit.sh
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
source ../.venv/bin/activate
echo "Running Composer edit audit..."
python audit_report.py | tee ../reports/audit_$(date +%Y%m%d_%H%M%S).txt
echo "Report saved to reports/"
Make it executable with chmod +x scripts/run_audit.sh and run it with ./scripts/run_audit.sh after every snapshot-capture session. Each run is timestamped and archived in reports/, which gives you a running history of how Composer’s edit behavior changes as Cursor ships new model versions.
Step 9: Add a CI Guardrail Against Unwanted Full Rewrites
If your team uses Cursor’s Agent mode to open pull requests directly, it is worth running the same classifier against real PR diffs so a reviewer gets flagged when an AI-authored change touches far more of a file than the PR description claims. Add a lightweight GitHub Actions job that runs the classifier against the PR’s changed files.
# .github/workflows/composer-rewrite-guard.yml
name: Composer Rewrite Guard
on: [pull_request]
jobs:
check-rewrite-ratio:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- run: pip install tiktoken rich
- name: Flag large changed-line ratios
run: |
git diff --name-only origin/main... | grep -E '\.(py|ts|tsx)$' | while read -r file; do
git show origin/main:"$file" > /tmp/before.py 2>/dev/null || continue
cp "$file" /tmp/after.py
python scripts/diff_classifier.py /tmp/before.py /tmp/after.py
done
This does not block a merge on its own; it prints the classification into the Actions log so a reviewer can see, before approving, whether an edit that was supposed to be a one-line fix actually rewrote 60 percent of a 500-line file. Teams that have already built custom Cursor tooling, such as the editor extension in our Cursor Hooks VS Code extension guide, can wire this same check into a pre-commit hook instead of waiting for CI. Teams that rely heavily on Composer’s Agent mode for unattended edits tend to find this most useful on their largest, highest-churn files, where a silent full rewrite is the most expensive to review by hand.
Step 10: Extend the Harness to Multi-File Agent Sessions
Everything above assumes a single file. Cursor’s Composer and Agent modes are commonly used for multi-file changes, where the apply model has to decide, file by file, whether each one gets a targeted diff or a rewrite. Cursor’s own forum has described how its apply model generates full file diffs for chat, Composer, and Agent sessions alike, which means the same classification logic applies per file; you just need to run it across every file touched in one session instead of one at a time.
# scripts/multi_file_audit.py
import glob
import os
from diff_classifier import classify_edit
def audit_session(before_dir, after_dir):
results = []
for before_path in glob.glob(f"{before_dir}/*"):
name = os.path.basename(before_path)
after_path = os.path.join(after_dir, name)
if os.path.exists(after_path):
results.append((name, classify_edit(before_path, after_path)))
rewrite_count = sum(1 for _, r in results if r["classification"] == "rewrite-equivalent")
print(f"{rewrite_count}/{len(results)} files classified as rewrite-equivalent")
return results
if __name__ == "__main__":
import sys
audit_session(sys.argv[1], sys.argv[2])
For a multi-file Composer or Agent session, capture every touched file’s before and after state into matching folders (for example snapshots/session1/before/ and snapshots/session1/after/) and point this script at both. The summary line at the end tells you, across an entire agent session, what fraction of touched files got the full-rewrite treatment, which is a much better signal for budgeting token spend on agent-heavy workflows than looking at any single file in isolation.
Step 11: Interpret Results and Decide When to Split Large Files
The point of all this tooling is a decision, not a dashboard. Once your audit report shows a consistent pattern of rewrite-equivalent classifications on files above roughly 400 to 500 lines, you have two practical options: keep the files as-is and accept the token and latency cost of occasional full rewrites, or split the largest, highest-churn files into smaller modules so more of your day-to-day edits land comfortably under the threshold where diff-style application tends to be both cheaper and more reliable. Neither option is universally correct; a 900-line file that changes twice a year is not worth refactoring, while a 900-line file that Composer touches daily almost always is.
| Changed-Line Ratio | Likely Classification | Practical Action |
|---|---|---|
| Under 15% | Targeted diff | No action needed; this is the cheap, reliable case |
| 15% – 35% | Borderline | Watch the trend across sessions before acting |
| 35% – 60% | Rewrite-equivalent | Tighten Cursor rules (Step 7) or shrink the request scope |
| Over 60% | Full rewrite | Consider splitting the file if this recurs weekly |
Run the audit for two to three weeks across your team’s real Composer sessions before deciding anything structural. A single noisy session is not a trend; a consistent pattern across dozens of real edits is.
It also helps to separate the decision by request type rather than judging an entire file by one aggregate score. A file can show a healthy low changed-line ratio on routine maintenance prompts (add a log line, tweak a default) while still spiking into rewrite-equivalent territory on structural prompts (change a function signature used throughout the file). Tag each audit entry with a rough request category, maintenance, structural, or exploratory, and you will usually find that splitting a file only helps with the structural category; maintenance-style edits tend to stay cheap regardless of file size.
| Request Category | Typical Changed-Line Pattern | Does Splitting the File Help? |
|---|---|---|
| Maintenance (log lines, defaults, comments) | Low and stable regardless of file size | Rarely necessary |
| Structural (signature changes, renames) | Spikes on large files specifically | Usually yes, if it recurs |
| Exploratory (“refactor this however makes sense”) | High on any file size | No; the prompt itself is the problem |
Common Pitfalls When Auditing Cursor’s Edit Behavior
- Comparing different models as if they were one. Switching Cursor’s model picker between sessions (say, from Composer 2.5 to a different frontier model) changes edit behavior independently of file size. Tag every snapshot with the model used, or your audit report will blend two different signals into one misleading number.
- Using inconsistent prompt wording across fixtures. “Add validation” and “please add basic input validation to each function” can produce meaningfully different changed-line ratios on the same file. Keep the prompt text identical across your size tiers.
- Treating the 35% threshold as Cursor’s actual internal cutoff. It is a proxy you chose in Step 3 to approximate practical impact, not a number Cursor publishes or guarantees. Recalibrate it against your own files instead of trusting the default blindly.
- Forgetting that whitespace and formatting changes inflate the changed-line ratio. If Composer reformats a file (reindents, reorders imports) alongside a small logical change, difflib will count that noise as “changed,” pushing a targeted edit into the rewrite-equivalent bucket. Diff against a pre-formatted baseline if your codebase runs an auto-formatter.
- Running the CI guardrail against the wrong base branch. The GitHub Actions example in Step 9 diffs against the repository’s default branch; if your default branch name or checkout depth is wrong,
git showwill silently fail and the script will skip files instead of erroring loudly. Add explicit error handling before relying on it for real reviews. - Assuming token savings translate directly into dollar savings. The estimate in Step 4 measures token count, not your actual Cursor plan’s billing model, which may price apply-model tokens differently from frontier-model tokens.
Troubleshooting Guide
- tiktoken fails to download an encoding on first run: cl100k_base is fetched from a remote cache the first time you call
get_encoding. If you are on a restricted network, pre-download it on an unrestricted machine and set theTIKTOKEN_CACHE_DIRenvironment variable to a local path you copy over. - Rich table renders without colors in CI logs: GitHub Actions’ log viewer does not always render ANSI color codes from Rich by default. Pass
force_terminal=TruetoConsole()if you need colored output to survive in CI logs specifically. - classify_edit() throws a UnicodeDecodeError: This usually means a snapshot file was saved with a BOM or non-UTF-8 encoding by your editor. Open both files with
encoding="utf-8-sig"instead of the default encoding. - Every fixture classifies as rewrite-equivalent, even xlarge_900.py: Check that your Composer prompt was not phrased as a request that genuinely spans the whole file (like “refactor this module”). A broad prompt legitimately produces a broad edit; narrow the prompt and re-test.
- The audit_report.py import fails with “No module named diff_classifier”: Run the script from inside the
scripts/directory, or add the folder to yourPYTHONPATH, since the import is relative to that folder. - git show in the CI workflow returns nothing for new files: A brand-new file has no prior version on the base branch, so
git showcorrectly returns nothing; the workflow’s fallback skips it, which is expected behavior, not a bug. - Snapshot pairs show identical before/after content: This typically means you copied the file into
snapshots/after Composer already applied the edit, so both “before” and “after” captured the post-edit state. Always copy the fixture before opening Composer, not after. - Diff hunk counts seem inflated relative to the changed-line percentage:
difflib.unified_diffcan split what is logically one contiguous change into multiple hunks when surrounding context lines differ slightly. Widen the context window parameter if you need hunk counts that match your intuition more closely. - The GitHub Actions job times out on large repositories: Fetching full history with
fetch-depth: 0on a very large repo can be slow. Switch to a shallow fetch with a fixed depth (for example 50 commits) if your PRs rarely need deeper history than that.
Advanced Tips for Tuning Fast Apply and Speculative Edits
Once the basic audit loop is working, a few refinements make the data more actionable for a team rather than a single developer. First, log the model name alongside every snapshot pair, not just the classification, so you can eventually compare how Composer 2.5 behaves against whichever model ships after it; Cursor’s product page lists several frontier models available in the picker, and edit behavior is not guaranteed to stay constant as that lineup changes.
Comparing Across Fast-Apply Providers
Cursor is not the only company building a dedicated fast-apply layer. Independent write-ups comparing fast-apply models note that Cursor, Morph, and Relace each publish their own speed and accuracy numbers, measured on different internal datasets and different hardware, which makes head-to-head comparisons between vendors unreliable unless you run them yourself on identical inputs. If your team evaluates more than one AI coding tool, perhaps splitting work between Cursor and Claude Code the way we described in our Cursor and Claude Code workflow setup, the same classifier and token-cost scripts from Steps 3 and 4 can be pointed at snapshots from any of them, as long as you can get a clean before/after file pair out of the tool. That gives you one consistent measurement, on your own code, instead of trying to reconcile three vendors’ marketing benchmarks that were never meant to be compared against each other in the first place.
One practical nuance worth tracking separately: a tool’s fast-apply pipeline and its reasoning model are not always versioned together. Cursor can ship an update to its apply model without changing which frontier model is selected in the picker, and that update alone can shift your changed-line ratios on the same prompts. If you see a sudden shift in your rolling audit log with no change in your own prompts or Cursor rules, check Cursor’s changelog for an apply-model update before assuming your fixtures or methodology broke.
Second, consider weighting your changed-line ratio by function boundaries instead of raw lines. A 400-line file with a 30 percent raw changed-line ratio might really mean “two of twelve functions were touched,” which is a much more useful framing for a reviewer than a percentage. A simple AST-based pass with Python’s built-in ast module can map changed line ranges back to function names before you report them.
Third, remember that Cursor’s search/replace-block mechanism, as described in its own engineering write-up on instant apply, parses diff hunks by replacing lines that start with a minus or a space with lines that start with a plus or a space, which means the representation Composer actually emits internally is neither a raw unified diff nor a literal full-file rewrite in every case. Your classifier works from the final file state because that is the only thing you can reliably observe from outside Cursor’s pipeline, so treat “targeted-diff” and “rewrite-equivalent” as behavioral labels describing the end result, not literal claims about which internal code path Cursor used.
Finally, if your team edits the same handful of large, high-churn files repeatedly, keep a rolling log of audit reports in the reports/ folder and review it monthly. A file that trends from mostly targeted-diff behavior to mostly rewrite-equivalent behavior over a few weeks is often a sign the file itself has grown past the point where any AI edit tool, Cursor included, can reliably scope a small change inside it.
The Complete Working Project
Putting every step together, the finished project folder looks like this:
composer-edit-audit/
├── .cursor/
│ └── rules/
│ └── scoped-edits.mdc
├── .github/
│ └── workflows/
│ └── composer-rewrite-guard.yml
├── fixtures/
│ ├── small_50.py
│ ├── medium_150.py
│ ├── large_400.py
│ └── xlarge_900.py
├── snapshots/
│ ├── before_small_50.py
│ ├── after_small_50.py
│ └── ... (one before/after pair per fixture)
├── reports/
│ └── audit_20261014_091500.txt
├── scripts/
│ ├── generate_fixtures.py
│ ├── diff_classifier.py
│ ├── token_cost.py
│ ├── audit_report.py
│ ├── multi_file_audit.py
│ └── run_audit.sh
└── .gitignore
That is a complete, runnable project: generate fixtures, capture real Cursor Composer sessions against them, classify and cost each edit, automate the loop, and gate your CI on the result. Nothing in it depends on an undocumented Cursor API, which means it keeps working across model updates as long as Cursor keeps letting you open and save files normally inside the editor.
Frequently Asked Questions
Does Cursor Composer always rewrite the entire file when it makes an edit?
No. Cursor’s apply pipeline can produce either a full-file rewrite or a more targeted diff-style edit depending on the model, the file size, and the complexity of the requested change. There is no public toggle that always picks one or the other; the audit tool in this tutorial exists specifically because that choice is not directly visible to the user.
What is Cursor’s fast apply model?
It is a separate, smaller model from Composer whose job is to materialize a proposed code change inside the actual file. Cursor has reported fast-apply speeds of roughly 1,000 tokens per second on a 70-billion-parameter apply model, which is why edits can feel near-instant even when the underlying change is large.
What are speculative edits in Cursor?
Speculative edits are Cursor’s code-editing adaptation of speculative decoding. The existing source file is used as a deterministic draft, so the apply model only has to verify which tokens stayed the same rather than regenerate the entire file from nothing, which Cursor says can make a full-file-equivalent application up to 9x faster.
Can I force Cursor to always use diffs instead of full rewrites?
Not directly. You can influence the scope of changes Composer proposes using project rules (Step 7), which tends to reduce the practical blast radius of an edit, but the internal decision about how the apply model represents that edit happens inside Cursor’s own pipeline and is not exposed as a user-facing setting.
Why do unified diffs sometimes fail to apply correctly in AI coding tools?
Independent analysis of coding agent edit formats, such as the comparison published on why AI tools skip standard diffs, has found unified-diff approaches landing around 70 to 80 percent accuracy on complex or heavily evolved files, largely because a patch’s context lines can fail to match the current file state exactly. That is one reason Cursor built a custom search/replace-block mechanism rather than relying purely on standard unified-diff patching.
How many lines is “too many” for a reliable full-file rewrite?
A commonly cited rough boundary is around 400 lines. Below that, full-file rewrites tend to stay reliable at an acceptable token cost; above it, diff-style application tends to become the more economical and dependable option, though this varies by file complexity and model.
Does this audit project work with AI coding tools other than Cursor?
Yes, with no changes. The classifier and token estimator only look at before/after file states on disk; they have no dependency on which tool produced the edit. You could just as easily use it to compare edit behavior across Cursor, GitHub Copilot, or any other AI coding assistant that lets you capture snapshots, a pattern also explored in a case study on designing coding agents across several editors, and one that pairs well with the CLI-level comparison in our Codex CLI vs Claude Code breakdown.
How do I reduce token costs when editing large files in Cursor?
Narrow your prompt scope so Composer has less reason to touch unrelated parts of the file, add a project rules file asking for the smallest possible change on large files, and consider splitting files that consistently classify as rewrite-equivalent in your audit report into smaller modules that naturally stay under the size threshold where targeted edits are more common.





