Working inside Cursor / Claude Code / Aider / Copilot
Cursor: the diff envelope
When you're prompting an LLM inside an IDE — Cursor, Zed, IntelliJ AI Assistant, JetBrains AI — the model isn't free to chat. It needs to produce output the IDE can parse and apply directly to your buffer. The right output shape is a unified diff wrapped in a code block.
This module's flow:
Module 5 — one skeleton, five output envelopes
Unified diff the IDE applies to your buffer
SEARCH/REPLACE — exact text, no line numbers
Plan, approve, then execute — with a real permission gate
No prompt at all. The file above the cursor is the prompt
Conventional commit generated from the diff
Cursor's user-message → diff round-trip:
The round-trip, and the two places it can break
A short system prompt + a focused user request gets you there. Here's the system prompt that makes the model behave like an IDE-native pair:
You are an expert pair programmer working inside an IDE. The user shows you code; you respond with a unified diff (
---/+++/ hunk markers) only — no prose unless explicitly asked. Always preserve unrelated lines exactly.
Now the user message is just the file and the change request:
File:
src/utils/email.tsexport function isValidEmail(s: string): boolean { return s.includes("@") && s.includes("."); }Task: tighten the validation. Reject leading/trailing whitespace, require at least one char before "@", require a dot AFTER "@", and a TLD of at least 2 letters. Keep the same signature.
Claude Sonnet 4.5 returned:
--- src/utils/email.ts
+++ src/utils/email.ts
@@ -1,3 +1,11 @@
export function isValidEmail(s: string): boolean {
- return s.includes("@") && s.includes(".");
+ if (s !== s.trim()) return false;
+
+ const atIndex = s.indexOf("@");
+ if (atIndex <= 0) return false;
+
+ const domain = s.slice(atIndex + 1);
+ const lastDotIndex = domain.lastIndexOf(".");
+ if (lastDotIndex === -1) return false;
+
+ return domain.slice(lastDotIndex + 1).length >= 2;
}
Captured from Claude Sonnet 4.5 (claude-sonnet-4-5) on 2026-04-27. Re-runs may differ slightly. See the model-lifecycle note in Module 1, lesson 1.
The logic is right. Every case in the task passes. And this diff does not apply. Save it to a file and run it:
$ git apply -p0 email.diff
error: corrupt patch at line 17
$ patch -p0 < email.diff
patch: **** malformed patch at line 16: }
Read the hunk header: @@ -1,3 +1,11 @@. It claims the replacement spans 11 lines. Count the hunk body — one context line, ten + lines, one closing } — and it is 12. Both git apply and patch verify that count before touching your file, and both refuse.
This is the single most useful thing in the lesson, so do not skim past it. A unified diff carries two kinds of correctness, and they fail independently:
| Layer | What it means | How it fails | Who catches it |
|---|---|---|---|
| Semantic | Does the new code do the right thing? | Wrong logic, wrong edge case | You, in review |
| Structural | Are the hunk offsets and line counts internally consistent? | Off-by-one line count, wrong @@ header | The patch tool, before it writes anything |
A model that is good at the first is not automatically good at the second, because the second is arithmetic performed over its own output — it has to count the lines it is about to emit before it emits them. That is precisely the kind of bookkeeping language models are weakest at, and it is invisible when you read the diff, because your eye reads the code and skips the @@ line.
Change +1,11 to +1,12 and the same diff applies cleanly. Nothing else needs to move.
What to do about it, in order of preference:
- Let the tool count. Cursor and most IDE assistants apply model edits through a fuzzy matcher that re-anchors on surrounding context rather than trusting the header, so the broken header often survives inside the IDE and then fails the moment you export the diff, paste it in a PR, or pipe it to
git apply. The envelope you can hand to another tool is the one that has to be right. - Ask for whole functions, not hunks, when the change is small.
Output the complete rewritten function in a single code blockhas no offsets to get wrong. - Verify before you trust.
git apply --checkis instant and exits non-zero on a bad patch:
git apply --check my-change.diff && echo "applies cleanly"
Add that one line to your loop and structural errors stop reaching your branch.
Semantically, the output is clean: no prose to strip, no explanation to ignore, no alternative implementations. That is what the format constraint bought. It just did not buy a diff that applies.
The system prompt is doing four things at once:
| Phrase | Effect |
|---|---|
| "expert pair programmer" | Sets the persona toward concise senior-engineer voice |
| "working inside an IDE" | Frames the output for tool consumption |
| "unified diff only" | Locks the output format |
| "preserve unrelated lines exactly" | Stops the model from sneaking in formatting changes |
That last constraint matters more than it looks. Without it, the model often "improves" indentation, changes single quotes to double quotes, or adds a missing trailing newline. Each of those creates noise in the diff and in the eventual git history. The constraint forces the diff to contain only your intended change.
A useful extension: ask the model to also add the test in the same diff:
Task: tighten the validation as described, AND add a unit test in
src/utils/email.test.tscovering the new rules. Both changes in the same diff.
The diff envelope handles multi-file changes naturally — each file gets its own --- / +++ pair. The IDE applies all hunks together, and you get an atomic change.
When the diff comes back wrong, the fix is rarely "rerun with a better prompt." It's usually "show me what you tried, and apply the patch I describe." Cursor, Zed, and similar tools let you do this through their chat surface. The conversation pattern: model proposes diff → you apply mentally → you correct one specific line → model rewrites the diff. Three turns, one merged change.
The four IDE-style envelopes you'll meet across this module, side by side:
IDE-style prompt envelopes
Cursor / Zed
- Atomic multi-file change
- Reviewable hunk by hunk
- Line-number drift breaks stale diffs
- Tempts model to re-format unrelated lines
Aider
- Survives surrounding edits
- Apply fails loudly on misread
- One block per logical edit or rollback gets ugly
- Verbose for tiny one-line changes
Claude Code
- Plan-then-go gate prevents scope creep
- Agent self-checks invariants
- Long context required
- Failure mode is silent file edits without 'go'
Copilot / Tabnine
- Zero-friction in flow
- Improves with comments above cursor
- No control surface beyond the file
- Generic if function name is vague
Next up: the SEARCH/REPLACE block format used by Aider. :::
Sign in to rate