I Only Want to See the Lines the Agent Added: A Changed-Lines Convention Linter
I asked an agent for a small fix, it touched an old file, and the linter threw 13 warnings with no way to tell which were the agent's. Scoping the linter to only the added lines dropped 13 to 3 — about 77% less noise. Here is how to pull added lines out of a diff and check only those, with working code.
I asked an agent to "add a delete endpoint." The diff that came back was clean: one new function, nothing else. Out of habit I ran the pre-save lint, and my hand stopped.
Thirteen warnings.
Scanning down the screen, almost all of them were console.log and var I had written myself, years ago. Which of these lines had the agent actually added just now? By the time I had matched them one by one and confirmed that only three of the violations were newly introduced, half of my appetite for reviewing the change was gone.
The problem is not the agent. It is that linting the whole file makes the warning count scale with how much debt the file carries, not with how large the change is. In what follows we build a small thing that breaks that proportion: a changed-lines linter that inspects only the lines the agent touched. As an indie developer leaning on agents for more edits, I have found this one move pays off quickly.
Why "changed lines" instead of "the whole file"
Code review in the agent era has a different texture than before. When humans wrote one line at a time, the change and the file mostly lined up. New files had the current conventions baked in from the start; old files stayed old; the boundary was clear.
Agents cross that boundary without a thought. "Add one function next to this one" is enough for them to reach confidently into a ten-year-old legacy file. The result is that a five-line addition drags in hundreds of lines of accumulated old violations, all onto the same screen.
Run a whole-file lint here and the reviewer loses twice. First, the agent's genuinely new lines — the ones worth reviewing — drown in the noise of existing violations. Second, you give in to the "while we're here, let's fix the old ones too" temptation, let the agent rewrite beyond what you asked for, and watch the diff balloon.
Changed-lines scope untangles both. Limit the judgment to "lines added in this diff." The legacy debt stays exactly where it is, but not a single newly added violation slips past. It is a deliberate narrowing: commit to not making the debt worse.
Pulling the added lines out of a diff
The heart of this is reading added lines, with their line numbers, out of git diff. The key is --unified=0, which suppresses the surrounding context lines. If context creeps in, unchanged lines end up in scope and the whole point of narrowing dissolves.
In the hunk header @@ -a,b +c,d @@, the +c is the starting line number in the new file. Count forward from there over the + lines and you have the real line number of each addition.
import re, subprocessdef added_lines(path): """Return (new_lineno, text) for each line the diff ADDED.""" out = subprocess.run( ["git", "--no-pager", "diff", "--unified=0", path], capture_output=True, text=True).stdout added, newno = [], None for ln in out.splitlines(): m = re.match(r'@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@', ln) if m: newno = int(m.group(1)) continue if newno is None: continue if ln.startswith('+') and not ln.startswith('+++'): added.append((newno, ln[1:])) newno += 1 elif not ln.startswith('-'): newno += 1 return added
Deleted lines, which start with -, do not exist in the new file, so we do not advance the counter for them. Count a deletion by mistake and every line number after it shifts. That is the quietly easy gotcha here.
Multiple hunks — say you edited two separate spots in the file — are handled too, because we reset newno every time we see a hunk header. Running it against a diff that edited one place near the top and another near the bottom, it picked up the added lines in both, each with the correct line number.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A diff parser that extracts only the ADDED lines, with their real line numbers
✦A changed-lines linter, and a measured drop from 13 warnings to 3 (about 77%) on a legacy file
✦Pairing an AGENTS.md advisory layer with a deterministic gate as a two-stage check
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once you can extract the added lines, the rest is just applying rules to them. Keep the rules as a small table of regexes. You will be adding your team's own rules over time, so make it easy to grow.
RULES = [ ("no-console", re.compile(r'\bconsole\.(log|debug)\b'), "no leftover console.log"), ("no-var", re.compile(r'(^|\s)var\s'), "use const/let, not var"), ("no-any", re.compile(r':\s*any\b'), "avoid the any type"), ("no-trailing-ws", re.compile(r'[ \t]+$'), "no trailing whitespace"),]def lint(lines): hits = [] for no, text in lines: for rid, rx, msg in RULES: if rx.search(text): hits.append((no, rid, msg, text.rstrip())) return hits
Whether you pass lint() the whole file or just the added lines, the same rules give you two modes. That symmetry is worth keeping on purpose: whenever you worry that "changed-lines mode is too lenient," you can check it against whole-file mode. The rules are shared; only the scope differs.
Measured: how much does the noise drop?
Let's confirm it by hand. I set up a legacy TypeScript file scattered with console.log, var, and any. Then a diff in which an agent adds one deleteUser function — and inside that new function I deliberately planted an any, a console.log, and a trailing space.
Here is the same rule set run in both modes.
Mode
Warnings found
Breakdown
Whole file
13
10 pre-existing + 3 from this change
Changed lines only
3
only the 3 from this change
Warnings dropped from 13 to 3 — about 77% less noise. The three that changed-lines mode caught were the any type, the trailing whitespace, and the console.log — all inside lines the agent had just written. The existing ten dropped out of view. Or more precisely: they were deliberately set aside as out of scope, not lost.
The gap grows with the file's debt. Here it was 13 versus 3; for a legacy file of several hundred lines, whole-file mode easily emits three digits of warnings, and you go hunting with your eyes for the few that matter. Changed-lines mode always returns only "what this change added." Review time gets decoupled from the amount of debt.
Pairing it with AGENTS.md
The linter works on its own, but it comes into its own as a two-stage check alongside AGENTS.md.
Stage one is advice. Write the same conventions into AGENTS.md in plain words, so the agent honors them from the start.
# AGENTS.md (excerpt)## Code conventions- No leftover console.log / console.debug (remove debugging before commit)- No var. Prefer const; use let only when reassignment is required- Avoid any. When the type is unsettled, take it as unknown and narrow- No trailing whitespace
The advisory layer alone makes the agent's output noticeably cleaner. But advice is probabilistic. It is followed often, not guaranteed. On long sessions, or when the instructions get tangled, it slips through.
So stage two places the linter as a deterministic gate. Right after the agent finishes an edit, or in a pre-commit hook, run the changed-lines linter and exit non-zero on any violation. The three that advice missed get caught here, reliably.
Layer
Mechanism
Nature
Role
Advisory
Conventions in AGENTS.md
Probabilistic
Keep violations from arising at all
Gate
Changed-lines linter (exit code)
Deterministic
Refuse to let a slipped violation through
Neither stage is enough alone. With only the gate, the agent keeps writing the same violation and getting bounced, over and over — inefficient. With only the advice, some slip through. Advice shrinks the volume; the gate closes the last gap. It is the pair that finally lets you take your hands off. I recommend standing up both stages before you put any of this into production.
Operational notes
After running this for a while, here are a few things worth keeping in mind.
Accept that it is weak on renames and moves. A change that merely relocates a block appears in the diff as delete-plus-add. If the added side carries pre-existing violations, the changed-lines linter picks them up as new. This is not a false positive — it is exactly what the scope definition says. For move-heavy refactors, it is practical to run whole-file mode alongside it, or to relax the linter temporarily to work around it.
Make the exit code a CI contract. Run the same script in both a local pre-commit hook and CI, and exit 1 on any violation. Don't rely on a human "being careful" — let the machine stop it. In agent operations especially, moving the judgment outside human attention is what pays off.
Start small and add only the rules that hurt. Line up 30 rules from day one and you will spend all your time tuning false positives, and you won't keep it up. Turn the items you flagged repeatedly in review into regexes, one at a time. That is why the table grows with a single added line. I recommend this "pain-driven" way of growing the rule set.
Know that "changed lines only" is a design that tolerates debt. This mechanism deliberately ignores old violations. Paying down debt belongs in its own task, done with intent and a plan. Narrow this tool's job to one thing: not adding new debt in day-to-day agent edits. Not getting greedy about coverage is, I think, the condition for keeping it alive.
It is a small script, but the "which of these was the agent's?" hunt that I used to feel on every review simply vanished, and the work got a good deal lighter in the hand. If you carry the same annoyance, I hope this is a nudge to bring your eyes back to the changed lines. Thank you for reading.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.