AI Broke Code Review. Here’s How to Rebuild It
- AI, Architecture, Clean Code, Mobile, Swift, SwiftUI
- 23 min read
You’re deep in a tricky bug when Slack pings: a colleague needs a code review. You sigh and click through. 47 files changed. 2,800 lines added, 400 removed.
The size of the commit suggests the code wasn’t written entirely by hand. That suspicion is confirmed the moment you start scrolling. Typical AI slop: duplication everywhere, a chaotic mix of current best practices and patterns nobody’s used since iOS 13, three slightly different helper functions doing the same thing across three SwiftUI views. And you’re expected to read it all and suggest improvements while keeping up with your own work.
Welcome to the reality of code review in 2026 and beyond.
About a year ago, I wrote a post about how to do code reviews your team actually looks forward to, where I argued that code review is a dialogue we should approach with empathy. I still stand by that – but times have changed. Should you still write a kind, careful, teaching review of 2,800 lines someone else vibed out in fifteen minutes? Especially if the author will probably never read it?
Are developers reduced to the last guardians at the gate? Did we come all this way just to bounce between vibing out code and reviewing vibed-out code?
Let’s dive in and find out.
The Numbers Don't Lie
Let me throw some numbers at you. Not because I enjoy statistics (I don’t), but because they make a point you already suspect. According to the Stack Overflow 2025 Developer Survey, 84% of developers now use or plan to use AI tools, and 51% use them daily. GitHub Copilot crossed 20 million users in 2025 and now writes roughly 46% of the code its users ship. Add Cursor, Windsurf, and the wave of agentic IDEs, and it is reasonable to assume that on a typical iOS team in 2026, more than half the code from your last sprint was generated by AI.
The funny part? Trust is going the opposite way: the same Stack Overflow survey found trust in AI accuracy dropped to 29%, down from 40% a year earlier. The more we use it, the less we trust it. So we generate more code and trust it less, while the number of human reviewers and the hours in their day stay exactly the same. Or go down, if the company decides to downsize.
In my original post, I made the case for thorough, respectful, dialogue-driven reviews. But that advice assumed a human wrote the code slowly, and would learn from the feedback. When the code is generated by AI in fifteen minutes, does that kind of review still earn its keep? At first glance, it seems that it would not.
But what if we flip the whole thing around? Instead of relying on developers to clean up AI-generated slop during review, maybe the AI should clean up its own code before it ever reaches a human? In other words: if AI assisted us in writing the code, why aren’t we letting it assist us in reviewing it, enforcing development standards, ensuring comprehensive test coverage, and more?
That’s the shift this post is about. Instead of asking: How do humans review more code, faster (a losing race), we should ask: which steps of the review process can we upgrade so the code presented to developers is already polished, compliant, and well-written?
Let’s build that pipeline, one layer at a time.
So, What's Actually Breaking?
Before we build the pipeline, we need to be precise about what we’re building against. Because AI made code review worse is hardly a diagnosis. Here are the three concrete cases where the old playbook falls apart. And you’ve probably lived through all of them.
- Nobody is really reading the code:
Start with the most uncomfortable truth. The author didn’t write the code – they prompted it. They (hopefully) skimmed the output, saw it compiled, and opened the PR. Now it’s on your proverbial plate.
So what happens? The review quietly falls to other people – who also have their own work to do. To be a team player, you skim for force unwraps, leave two comments, and approve. The other human reviewers do the same, and the bots chime in. Everyone assumes someone else is doing the careful pass. Nobody is.
Of course, this also happened with hand-written code. But those PRs were smaller – produced slowly by someone who could explain every line. AI removed that ceiling. The volume exploded. The awareness budget didn’t. - The spec never kept up:
TLDR: With AI, engineering velocity went up 3–5×. Product practice didn’t.
Tickets are still one-liners written in a hurry. Designs are still partial (or just wireframes). Acceptance criteria still live in someone’s head.
In the pre-AI era, this was a hard blocker: the developer would slow down, ask questions, and fill the gaps with judgment before starting any meaningful work. But an AI assistant will always produce something, regardless of the quality of the input. It will cheerfully invent the missing 90% of the scope. Plausibly, confidently, and, likely, wrongly.
Some developers argue that their job is not reviewing code anymore. It’s reverse-engineering what the feature was even supposed to do. - We pay for bigger models instead of writing better rules:
A team notices their AI output is inconsistent. The fix? Move to a pricier model. And likely bump the subscription to the max Usually both, just to be safe.
Almost nobody asks the cheaper question first: Did we ever actually tell the AI what our standards are?
Most teams have, at best, thin rules – often whatever Claude or Cursor auto-generated when first pointed at the codebase. As a result, an assistant is producing code in a vacuum. The output looks like an amalgamation of whatever passes as Swift on the internet, and it might not meet the standards established in your project. At least, not every time. Even if you get the LLM to produce the output you want eventually, the prompts that led to it are likely lost when you clear the session.
You can throw a $200/seat subscription at the problem, or you can spend an afternoon writing rules the whole team shares.
Three problems: code nobody reads, specs that don’t keep up, and rules nobody writes. The good news? Almost all of it can be handled before a human ever opens the PR. That’s the pipeline. Let’s look at its shape.
The Review Pyramid Needs More Layers
In the original post, I argued that most review remarks can be automated away with linters, formatters, and Danger. I compared an effective code review to a pyramid: automate the boring, mechanical work at the base so humans at the top can focus where it matters – architecture, intent, and trade-offs.
That idea is still correct, provided that it’s the humans who produce the code. Linters and formatters cast a wide enough net to catch the mechanical noise even a careful developer might leave behind. A stray typo, an inconsistent indent, a missing doc comment.
But AI rarely leaves behind a typo. It leaves behind 3k+ lines of mismatched, duplicated, confidently-wrong code. Several times a day. The old base is no longer sufficient. If linters and formatters are the only thing standing between raw AI output and a human reviewer, the human drowns. Which is precisely where most teams are right now.
So what do we do? We widen the base and add layers. We start thinking about code review as a pipeline – like the app build process in CI. A series of gates a change passes through, each catching what the one before it couldn’t. By the time code reaches a human, the mechanical, stylistic, standards-breaking, and obviously broken issues have already been handled.
Here’s the process I’ve landed on in my current project:
Five layers:
- Layer 1 – Linters & formatters: mechanical correctness. Deterministic, instant, basically free.
- Layer 2 – Per-module AI rules: teach the assistant your codebase before it writes a line.
- Layer 3 – Specialised review skills: automated review agents, each focused on a single concern.
- Layer 4 – Respond-to-review agent: triages the feedback, applies what’s valid, leaves a paper trail.
- Layer 5 – The human: architecture, intent, judgement. Finally.
The ordering of layers isn’t strictly chronological. AI rules shape code as it’s generated, while linters and formatters run on the final product.
That’s fine. This framework is organized by cost. Each layer is cheaper and more deterministic than the one above it. A linter is a fact. An AI review is a hypothesis. Human time is the most expensive. Aim to resolve as much as possible at the cheapest, most reliable layer. That reduces time spent higher up the stack, which means: less wasted effort, and fewer issues left for QA to find.
Let’s build it from the ground up.
Layers 1 & 2: The Boring Stuff
I’m going to move through the first two layers quickly. Neither is where the interesting problem lives.
- Layer 1 – linters and formatters.
I covered these in the original post, and everything there still holds. The only thing the AI era changes is the stakes:SwiftLintandSwiftFormatdon’t care that the author is a model and won’t hesitate to leave a mess behind. Run them as a gate, not a review comment. If a bot can tell you to add a space, a human should never have to. Fail the build instead. Preferably locally, e.g. by enabling the Treat warnings as errors flag in Xcode. - Layer 2 – per-module AI rules.
Remember the third symptom? Teams choosing pricier models instead of writing down what clean code looks like? This is the cure. I wrote a whole post on the topic: how to make AI enforce your coding standards. The short version: a well-written set of AI rules, skills, and agents turns a cheap model with context into something that beats an expensive model working blind. Spend some time defining what good enough means to you, formalize it for the AI, and keep improving the definition with your team each time you notice unsatisfactory output.
Both initial layers are cheap, well-trodden, and not where the challenge lies in building an effective code review pipeline. Nonetheless, they’re vital for ensuring that when code gets up the food chain, it’s free of the usual suspects: duplication, inconsistent style, and other avoidable issues.
The interesting question is what happens after the code is generated and before a human ever sees it. That’s where the new stuff lives.
Layer 3: Review Skills (Yes, Plural)
What’s the first thing that comes to mind when you imagine AI-driven code review? Most developers immediately think: Act as a senior iOS engineer, review this code, and suggest improvements. Fun fact: it actually works! Sort of. It leaves a handful of comments. Some are useful; most are generic (consider adding error handling), others are confidently wrong.
Why is that? You asked one reviewer to handle every aspect of the code: architecture, naming, test coverage, security, and more. It tried to do it all, giving each topic the same amount of attention. No human reviewer works that way, and neither should an LLM. Attention is finite, especially for AI models. Ask for everything, and you get a shallow pass on everything.
So don’t create one reviewer. Create several – each with a narrow charter and explicit list of what to ignore. That second part is critical. A scoped reviewer isn’t just: focus on architecture. It’s: focus on architecture, and do not comment on anything else: formatting, naming, tests, etc. Telling each skill what not to look at is what keeps it sharp and stops five skills from all leaving you the same vague comment about error handling.
Here’s what one of these looks like as a plain markdown skill file. Vendor-agnostic, they work whether you’re driving Claude Code, Cursor, or something else:
---
name: architecture-review
description: Single-lane PR review for architecture only -- module boundaries, MVVM/coordinator structure, dependency injection, and separation of concerns. Terse BLOCKING/NON_BLOCKING findings. Ignores style, tests, docs, accessibility, and correctness bugs. Loaded by the focused-reviewer agent during /review-pr.
---
# Skill: Architecture Review
You are a **terse, clinical architecture reviewer**. You care about one thing: does the change respect this project's architecture. No praise, no style feedback, no rationale fluff.
## When to run
On any change that adds or modifies types in the app target or a feature module -- before opening a PR. Invoked per-lane by the `focused-reviewer` agent.
## Authoritative sources -- read these first
The rules live in these docs; read them and enforce what they say (do not rely on this skill to restate them):
- `CLAUDE.md` (repo root) -- Hard rules, incl. the module-import boundary.
- `.ai/rules/core/code-style.md` -- Import management.
- `.ai/rules/architecture.md` -- Coordinator/MVVM/modular-package structure, DI.
...
## Focus -- only these
Review the **review set** against the sources above (the diff in `diff` scope; every file in a `REVIEW_SET: module` audit) for:
- **Module-boundary imports** -- anything reaching past a module's public interface...
- **Dependency injection** -- dependencies constructor-injected, not resolved inline in production code.
- **Duplication vs premature abstraction** -- copy-pasted logic that should be shared, or a one-off abstraction that should be inlined.
...
## Explicitly ignore
- Formatting, naming, spacing, MARK comments, optional-binding shorthand -- the `lint-review` owns those.
- Correctness/crashes/memory/concurrency -- `correctness-review` owns these.
- Test structure/coverage -- `unit-test-review` / `ui-test-review`.
...
## Tone & severity
- `BLOCKING` -- breaks a module boundary, injects dependencies illegally, or leaks a whole layer's responsibility into the wrong type.
- `NON_BLOCKING` -- architectural smell worth fixing but not structurally breaking (minor duplication, borderline placement).
## Strictness -- high-impact lane: bias toward flagging
This is a high-impact lane. Err on the side of catching problems:
- When unsure whether something is a real issue, **flag it** -- a missed architecture defect costs far more than a false positive here.
- Prefer the **more severe** of two plausible readings of the same code.
- Resolve severity ties **upward**; do not silently drop a real issue as "probably fine".
...
## Finding format
One finding per line, in this exact canonical format:
FILE:LINE | SEVERITY | architecture-review | CATEGORY | TITLE | WHY | FIX
- `CATEGORY` ∈ `Boundary`, `MVVM`, `DI`, `Coordinator`, `UseCase`, `Duplication`.
- `TITLE` ≤ 8 words. `WHY` ≤ 1 sentence. `FIX` = concrete instruction or snippet.
...---
name: architecture-review
description: Single-lane PR review for architecture only -- module boundaries, MVVM/coordinator structure, dependency injection, and separation of concerns. Terse BLOCKING/NON_BLOCKING findings. Ignores style, tests, docs, accessibility, and correctness bugs. Loaded by the focused-reviewer agent during /review-pr.
---
# Skill: Architecture Review
You are a **terse, clinical architecture reviewer**. You care about one thing: does the change respect this project's architecture. No praise, no style feedback, no rationale fluff.
## When to run
On any change that adds or modifies types in the app target or a feature module -- before opening a PR. Invoked per-lane by the `focused-reviewer` agent.
## Authoritative sources -- read these first
The rules live in these docs; read them and enforce what they say (do not rely on this skill to restate them):
- `CLAUDE.md` (repo root) -- Hard rules, incl. the module-import boundary.
- `.ai/rules/core/code-style.md` -- Import management.
- `.ai/rules/architecture.md` -- Coordinator/MVVM/modular-package structure, DI.
...
## Focus -- only these
Review the **review set** against the sources above (the diff in `diff` scope; every file in a `REVIEW_SET: module` audit) for:
- **Module-boundary imports** -- anything reaching past a module's public interface...
- **Dependency injection** -- dependencies constructor-injected, not resolved inline in production code.
- **Duplication vs premature abstraction** -- copy-pasted logic that should be shared, or a one-off abstraction that should be inlined.
...
## Explicitly ignore
- Formatting, naming, spacing, MARK comments, optional-binding shorthand -- the `lint-review` owns those.
- Correctness/crashes/memory/concurrency -- `correctness-review` owns these.
- Test structure/coverage -- `unit-test-review` / `ui-test-review`.
...
## Tone & severity
- `BLOCKING` -- breaks a module boundary, injects dependencies illegally, or leaks a whole layer's responsibility into the wrong type.
- `NON_BLOCKING` -- architectural smell worth fixing but not structurally breaking (minor duplication, borderline placement).
## Strictness -- high-impact lane: bias toward flagging
This is a high-impact lane. Err on the side of catching problems:
- When unsure whether something is a real issue, **flag it** -- a missed architecture defect costs far more than a false positive here.
- Prefer the **more severe** of two plausible readings of the same code.
- Resolve severity ties **upward**; do not silently drop a real issue as "probably fine".
...
## Finding format
One finding per line, in this exact canonical format:
FILE:LINE | SEVERITY | architecture-review | CATEGORY | TITLE | WHY | FIX
- `CATEGORY` ∈ `Boundary`, `MVVM`, `DI`, `Coordinator`, `UseCase`, `Duplication`.
- `TITLE` ≤ 8 words. `WHY` ≤ 1 sentence. `FIX` = concrete instruction or snippet.
...Notice the last section. No praise, no summary, just findings and their priorities. If a human were the intended audience, being empathetic would be appreciated. But the skill’s output will be assessed by an AI agent. It doesn’t have feelings. We can safely trim the output to save tokens and reading time.
Now build a small set of these. Mine are roughly: architecture, correctness, testability, UI conventions, and accessibility – though the exact set is yours to define. Each is relatively short, readable markdown. Each references the relevant post or rules file so it reviews against project standards, not the internet’s average. And you run only the ones a given change actually needs – a pure UI tweak doesn’t need the architecture reviewer.
The Aggregator: One Report, Not Seven
You might be asking: Do I have to read through all these targeted reports and determine which findings are worth fixing? Of course not!
Enter the aggregator agent: a conductor leading an orchestra. It fans review lanes out in parallel, gathers each lane’s findings, and delivers a single report. One command in, one prioritised list out. It’s still Layer 3, though – findings are just reported, not fixed.
The most valuable thing the aggregator does isn’t the fan-out. It’s the validation. AI reviewers can hallucinate, even when constrained by project rules and guidelines. They can flag code that isn’t there or cite rules that don’t apply. Left unchecked, that noise is exactly why developers stop trusting AI review.
So before a finding ever reaches you, the aggregator confirms it points at existing, modified code and that it genuinely breaks the rule it cites. Findings that are… inaccurate are silently dropped. The rest are de-duplicated and assigned a cross-lane priority.
Here’s the shape of mine, trimmed right down and stripped of anything project-specific:
---
name: review-aggregator
description: Fans out one focused reviewer per skill in parallel,
validates and deduplicates their findings, assigns cross-lane
priority (functional/architectural over cosmetic), and returns
one prioritised report. Optionally applies safe fixes.
---
## Lanes (one focused reviewer each, run in parallel)
| skill | run when |
|----------------------|----------------------|
| architecture-review | always |
| correctness-review | always |
| unit-test-review | always |
| swiftui-review | always |
| accessibility-review | always |
| localisation-review | only if copy changed |
| lint-review | always |
## After the lanes return
1. Validate -- confirm each finding points at real, changed code and
truly breaks the cited rule. Drop hallucinations and misreadings.
2. Deduplicate -- same file:line across lanes → keep the most severe.
3. Prioritise --
P1 must-fix: crashes, data loss, module-boundary/DI breaks, leaks
P2 should-fix: tests, accessibility, non-blocking correctness
P3 minor: docs, lint, nitpicks (grouped, not itemised)
4. Present one report. With --fix, apply only the safe,
no-logic-change subset, showing a diff before each edit.---
name: review-aggregator
description: Fans out one focused reviewer per skill in parallel,
validates and deduplicates their findings, assigns cross-lane
priority (functional/architectural over cosmetic), and returns
one prioritised report. Optionally applies safe fixes.
---
## Lanes (one focused reviewer each, run in parallel)
| skill | run when |
|----------------------|----------------------|
| architecture-review | always |
| correctness-review | always |
| unit-test-review | always |
| swiftui-review | always |
| accessibility-review | always |
| localisation-review | only if copy changed |
| lint-review | always |
## After the lanes return
1. Validate -- confirm each finding points at real, changed code and
truly breaks the cited rule. Drop hallucinations and misreadings.
2. Deduplicate -- same file:line across lanes → keep the most severe.
3. Prioritise --
P1 must-fix: crashes, data loss, module-boundary/DI breaks, leaks
P2 should-fix: tests, accessibility, non-blocking correctness
P3 minor: docs, lint, nitpicks (grouped, not itemised)
4. Present one report. With --fix, apply only the safe,
no-logic-change subset, showing a diff before each edit.Two touches worth considering.
First, the strict lanes – correctness, architecture, etc. I run twice, with the second pass explicitly told to assume the first one missed a crash or a retain cycle and to go hunting for it. A single pass does occasionally miss real defects. If we make a second, cheap pass – we should be OK catching most of them.
Second, after everything is pooled, one final critic pass asks: What class of bug is plausibly here but not represented in these findings? The only step in the whole pipeline that can add something rather than remove it.
The optional --fix mode is where the aggregator finally gets its hands dirty. It’s only allowed to apply fixes that are safe and don’t change behavior: shorthand optional binding, a missing [weak self] where self is already safely unwrapped, and similar tweaks. Anything that could affect real business behavior stays on the list for a human to assess. The rule I stick to: never auto-apply a change that could alter how an app feature works. Cosmetic and mechanical, yes. Logic, never.
If it isn’t already obvious, this review flow is meant to run before you create a PR. Issues are caught and fixed in a tight local loop, where the cost of a fix is minutes. Not a round-trip through CI, GitHub, and a human’s inbox that takes hours. By the time the PR opens, the architecture, testability, and security reviewers have already had their say. And it’s been acted on.
This is how the final result may look like for you:
Unfortunately, some findings will inevitably be wrong. And so will some of the comments that land on the PR once it’s open. If an agent is going to act on feedback automatically, how do you stop it “fixing” things that were fine? That’s Layer 4.
Layer 4: Don't Just Apply the Feedback
So the review skills have run, you’ve read the report, applied the fixes, and opened a PR. As expected, even after the local review, comments start landing on it. From a bot like BugBot, from Copilot, maybe even from a human who got there early. It doesn’t matter.
The seemingly obvious move is to point the AI at the PR, let it fetch every remark, and apply the fixes. I wouldn’t recommend it though.
Not all review feedback is valuable (shocking, I know). Even the best review bots make mistakes (even more shocking). A bot might suggest a fix that quietly breaks a rule your team agreed on months ago. Two comments might contradict each other. One suggestion might be technically sound but still violate the pattern the rest of the module follows. And the longer the PR, the harder it is for a bot to stay consistent.
Why? Because bots try to be all-in-one review machines. They have to assume the author could be a seasoned veteran or a secondary-school student learning to code, so the feedback is often poorly scoped. You can try to steer a bot with extra prompts, but that rarely overrides its core instructions. And few bots bother to read a project’s .ai folder, or even the usual suspects like CLAUDE.md and your Cursor rules. Assuming the repo has them at all… Let alone real documentation of its architecture decisions and standards.
Still, bots do surface genuinely valuable suggestions. So how do we fish those out of the sea of noise? You guessed it – more AI automation. This time a command, not an agent.
That distinction matters. Layer 3’s aggregator is an agent because review is open-ended – you want it to explore. Responding to review is the opposite: it should be structured, precise, and predictable. The same steps in the same order. Every time. That’s a command.
Mine is called handle-code-review-remarks, and for each remark it does three things:
- Restate the suggestion:
A read-only analyser reads the comment and the surrounding code, then states what the remark is actually asking for. Half of the bad remarks fall apart the moment you state them plainly like this. Welcome to the 2026 edition of rubber-duck debugging! - Check it against the rules:
Does the suggestion contradict a team, project, or org standard, or a documented decision? The analyser has access to the entire .ai folder and the rules files to verify this. - Decide:
Apply it (partially or fully), decline it, or escalate to a human.
Once every remark has been judged, the command replies on the thread explaining what it did and why. That reply is the whole point. It’s the paper trail.
Here’s the command, trimmed down and stripped of anything project-specific. Adjust it to your own setup:
# Handle Code Review Remarks -- command
Fetches unanswered BOT review comments (Copilot, BugBot, any login
ending in [bot]), analyses each for validity, applies fixes for the
valid+safe ones, verifies no regressions, then replies to every
comment it processed.
Human inline comments are intentionally skipped -- a person answers
those manually. The bots argue with the bots; humans are left alone
until it's their turn.
## Step 1 -- Get the PR
[ Parse the PR URL, derive owner/repo/number, fetch the author, etc. ]
## Step 2 -- Fetch comments and filter down
[ Pull every review comment, then subtract the ones we shouldn't
touch: already-resolved threads, comments already replied to,
pure "nice work 🎉" positivity, and -- crucially -- anything from a
human. What's left is `to_process`: unanswered bot remarks only. ]
If `to_process` is empty: print "No unanswered bot remarks." and stop.
## Step 3 -- Get the diff for context
[ Fetch the PR diff once, store it, pass it to Step 4 so the analyser
has full context without extra API calls. ]
## Step 4 -- Analyse (read-only agent, NO file edits)
Launch a read-only analyser. Its only job is to classify -- it must
not touch a single file. For each comment it returns one block:
REMARK_ID:
FILE:
LINE:
STATUS: valid | partial | declined
ACTION: valid/partial → exactly what to change and why
declined → the concise technical reason
## Step 5 -- Implement fixes (the safe ones only)
For each `valid`/`partial` item:
1. Re-read the file to confirm the code matches the context.
2. Apply the change.
3. Apply fixes one at a time, so one failure can't corrupt the rest.
STOP conditions -- escalate to human instead of editing, if the fix
would:
- change app architecture or module structure,
- alter existing logic or control flow,
- touch a public API, data model, or a load-bearing assumption.
A correct remark is NOT enough to auto-apply. It must also be safe
and local. When you escalate, propose the fix(es) and clearly
recommend one -- leave the decision, not the homework, to the human.
## Step 6 -- Verify no regressions
Run `make build`. If it breaks on a file you changed, revert that
file and mark the remark `needs-human` with the error.
Format only the files you touched (not the whole project). Run the
relevant tests; on failure, revert that file and mark `needs-human`.
## Step 7 -- Implementation gate
Before replying: for every `valid`/`partial` outcome, confirm an edit
was actually made. If not, retry or downgrade to `needs-human`. No
claiming "fixed" without a matching edit.
## Step 8 -- Reply on every processed thread
valid → "Fixed -- ."
partial → "Partially addressed -- .
left, and why>."
declined → "Not applied -- ."
needs-human → "Skipped --
be applied>." + the recommended fix.
## Step 9 -- Summary
[ Print a table: comment, status, action, reply posted. List files
modified and everything flagged needs-human. ]# Handle Code Review Remarks -- command
Fetches unanswered BOT review comments (Copilot, BugBot, any login
ending in [bot]), analyses each for validity, applies fixes for the
valid+safe ones, verifies no regressions, then replies to every
comment it processed.
Human inline comments are intentionally skipped -- a person answers
those manually. The bots argue with the bots; humans are left alone
until it's their turn.
## Step 1 -- Get the PR
[ Parse the PR URL, derive owner/repo/number, fetch the author, etc. ]
## Step 2 -- Fetch comments and filter down
[ Pull every review comment, then subtract the ones we shouldn't
touch: already-resolved threads, comments already replied to,
pure "nice work 🎉" positivity, and -- crucially -- anything from a
human. What's left is `to_process`: unanswered bot remarks only. ]
If `to_process` is empty: print "No unanswered bot remarks." and stop.
## Step 3 -- Get the diff for context
[ Fetch the PR diff once, store it, pass it to Step 4 so the analyser
has full context without extra API calls. ]
## Step 4 -- Analyse (read-only agent, NO file edits)
Launch a read-only analyser. Its only job is to classify -- it must
not touch a single file. For each comment it returns one block:
REMARK_ID:
FILE:
LINE:
STATUS: valid | partial | declined
ACTION: valid/partial → exactly what to change and why
declined → the concise technical reason
## Step 5 -- Implement fixes (the safe ones only)
For each `valid`/`partial` item:
1. Re-read the file to confirm the code matches the context.
2. Apply the change.
3. Apply fixes one at a time, so one failure can't corrupt the rest.
STOP conditions -- escalate to human instead of editing, if the fix
would:
- change app architecture or module structure,
- alter existing logic or control flow,
- touch a public API, data model, or a load-bearing assumption.
A correct remark is NOT enough to auto-apply. It must also be safe
and local. When you escalate, propose the fix(es) and clearly
recommend one -- leave the decision, not the homework, to the human.
## Step 6 -- Verify no regressions
Run `make build`. If it breaks on a file you changed, revert that
file and mark the remark `needs-human` with the error.
Format only the files you touched (not the whole project). Run the
relevant tests; on failure, revert that file and mark `needs-human`.
## Step 7 -- Implementation gate
Before replying: for every `valid`/`partial` outcome, confirm an edit
was actually made. If not, retry or downgrade to `needs-human`. No
claiming "fixed" without a matching edit.
## Step 8 -- Reply on every processed thread
valid → "Fixed -- ."
partial → "Partially addressed -- . ."
declined → "Not applied -- ."
needs-human → "Skipped -- ." + the recommended fix.
## Step 9 -- Summary
[ Print a table: comment, status, action, reply posted. List files
modified and everything flagged needs-human. ] Three things worth mentioning:
- The analyser is read-only.
It classifies but never edits. Keeping decide what to do separate from doing it prevents the command from talking itself into a bad change mid-stream. The same reason the aggregator’s reviewers were read-only too. - A correct remark is not a licence to change the code.
This is the line I care about most. The command happily applies safe, local, mechanical fixes. But the moment a fix would touch architecture, logic, a public API, or a load-bearing assumption, it stops and escalates, with a recommended fix attached. Even when the bot is completely right. A machine can tell you the seatbelt is unbuckled. It would not fasten it for you. - The command replies to every remark, providing its reasoning.
A decline with no rationale is just a bot being stubborn. A decline that says: Not applied – project DI conventions inject services rather than construct them inline. See XYZ for reference, is a bot being accountable. This way, developers reading the thread have all the context they need at a glance.
The result? Noisy 80% of review comments – the mechanical, the stylistic, the already-decided – get handled, answered, and closed before a human spends a minute on them. And the human comments? They were never touched, waiting for the one person who should answer them. What’s left when a reviewer finally opens the PR isn’t a wall of resolved trivia. It’s the handful of things that genuinely need a human brain.
Which brings us, at last, to the human reviewer.
Layer 5: The Human, Finally
Here’s what’s happened so far in the process:
- Linters and formatters caught the mechanical noise.
- The local review flagged architectural smells, missing tests, accessibility gaps, etc. A separate agent validated and ranked those findings to cut down on hallucinations.
- A developer decided which issues to address. And possibly re-ran the internal review, if the changes touched architecture or business logic.
- The draft PR was created and submitted for automated review.
BugBot,GitHub Copilot, and the others had their say. - The respond-to-review command fetched the remarks, validated them, assigned priorities, and fixed what it safely could, escalating anything that needed developer input. Every PR comment got a reply with an explanation and the context behind it.
- As soon as CI gives the thumbs up, the PR is ready for human review.
This is where you come in. You no longer have to scroll through dozens of trivial comments about spacing or force-unwrapping an optional. If they ever existed, the local review already fixed them. You likely won’t see the more serious issues either: excessive duplication, implementations that break project rules, internet-average solutions – all gone once the local review is done. And what about dozens of remarks from cloud agents like BugBot and Copilot? Handled, responded to and collapsed by the respond-to-review command. If you want, you can expand each one to see exactly how it was addressed. There’s a paper trail for every action taken!
So what’s left to do? The most important part: the human review. Answering the core questions:
Is this the right solution to the right problem?
Will this architecture still make sense in six months?
Should this be extracted into its own module?
Arguably, that’s the only type of review always worth doing. And it’s the one most developers no longer have time for, drowning in the menial work.
Remember the question from the top of this post? Is it worth spending an hour thoroughly reviewing 2,800 lines someone vibed out in fifteen minutes?
The answer is no. But you should absolutely spend twenty minutes on the 200 lines that actually matter. Rest assured the automated flow has taken care of the other 2,600.
Is that a demotion? Quite the opposite: you’re not the janitor mopping up AI slop. You’re the only person in the loop making the calls a machine isn’t allowed to make.
Sounds too good to be true? That’s because it partly is. Let’s talk about what can go wrong.
The Trade-offs (Because There Always Are Some)
The question you should be asking by now is the obvious one: what if the flow misses a retain cycle? What if it dismisses a genuine threading bug that Copilot actually caught? In short – what if it makes a serious mistake?
That’s true of any setup. Even the best-prompted agents on the most capable frontier models get things wrong. So the useful question isn’t whether a mistake happens – it’s what the consequences are when one does.
And the answer is: the same as without any of this. A serious bug that slips through the pipeline would have slipped past a lone reviewer too. Except now it had to get past linters, scoped review skills, a validating aggregator, and a respond-to-review command first. If it still makes it through, it gets caught the way it always was: by QA, or by internal users. The worst case is no worse than before. You’ve just added four more chances to avoid it.
So yes: AI makes mistakes. I won’t pretend the double-passes and the completeness critic reduce that to zero. But what’s the honest alternative? A tired human, under deadline, reviewing 5,000 lines vibed out in twenty minutes? They miss more, not less. Human attention doesn’t scale. By line #1523 and comment #42 nobody is reading carefully.
Software development was always a trade-off between moving fast and staying stable. Every team sits somewhere on that line whether they admit it or not. This flow makes it possible to maintain the speed AI gave the developers past the code generation process. It does so while admitting openly that you’ll occasionally let something through. Hand-written development let things through too. The pipeline doesn’t have to be perfect. It just has to beat a rushed human doing the job alone. At volume, that’s a low bar.
One trap to avoid: don’t treat the green ticks as proof. They’re not. They’re a cleaner starting point for the one review that still needs your judgement. Trust the pipeline to remove the noise, but not to make decisions. The line in the sand lies exactly between filtering and deciding.
Wrapping Up
AI writes most of our code now – which means more code, more bugs, less trust, and the same humans with the same hours to review it all. The old code review playbook buckles under that weight, and rushed reviews are hardly the answer.
Instead, we’ve discussed ways of filtering the code through layers before a human ever sees it. Linters kill the mechanical noise. Per-module rules keep the LLM inside your conventions as it writes. Scoped review skills each check for issues within a particular domain: architecture, thread-safety, UI best practices. Before the PR opens, an aggregator validates and ranks what they find. Then, once the cloud-based review bots have had their say, a respond-to-review command works through their remarks and replies to each with its reasoning. By the time the PR reaches a fellow developer, all that’s left are the decisions a machine isn’t allowed to make. Just like it was in the old days!
Naturally, there are no guarantees – every LLM layer can be wrong. But the worst case is no worse than a rushed human reviewing 5,000 lines alone, and you’ve added several chances to catch trouble along the way.
Where should I start? As in every AI-assisted development workspace, start with the rules. They’re an afternoon of work, and every layer above gets sharper the moment they exist. Move on to the next layer only once you’ve confirmed the LLM reliably generates code that conforms to them.
Don’t forget: the savoir vivre of opening a PR hasn’t gone anywhere. A human brain is far more likely to engage with 500 lines than 5,000. Truth be told, the LLM probably feels the same way. Smaller commits mean less noise, which means a higher chance of catching every issue before it reaches the human.
Finally, let’s circle back to where we started: my original post. I called code review a dialogue. That hasn’t changed. There are just more voices in the room now, with the machines doing the heavy lifting to clear the noise. When it is finally your turn to step in, the trivialities should be gone. What’s left to discuss is what always mattered the most: architecture, trade-offs, and project conventions.
Don’t miss any new blogposts!
By subscribing, you agree with our privacy policy and our terms and conditions.
Disclaimer: All memes used in the post were made for fun and educational purposes only
They were not meant to offend anyone
All copyrights belong to their respective owners

